From 7bdc5364df0646983f1ca9d657940692e576d9c5 Mon Sep 17 00:00:00 2001 From: Charles Malouin Date: Fri, 16 Feb 2024 21:03:44 -0500 Subject: [PATCH 1/2] Vimm's Lair parser rework This rework should in theory make the parser less prone to crash. --- src/main-window.vala | 12 +- src/meson.build | 4 +- src/models/game.vala | 3 +- src/models/system.vala | 36 +- src/utils/parser.vala | 811 ----------------------- src/utils/parsers/vimms-lair-parser.vala | 407 ++++++++++++ src/widgets/game-detail-dialog.vala | 6 +- src/widgets/search-row.vala | 3 +- 8 files changed, 428 insertions(+), 854 deletions(-) delete mode 100644 src/utils/parser.vala create mode 100644 src/utils/parsers/vimms-lair-parser.vala diff --git a/src/main-window.vala b/src/main-window.vala index 5a56506..0b1f159 100644 --- a/src/main-window.vala +++ b/src/main-window.vala @@ -305,6 +305,13 @@ namespace RetroPlus { } void on_download_started (Models.Game game, Models.Media media) { + if (game.missing || game.removed) { + var toast = new Adw.Toast (_("%s is currently missing/unavailable").printf (game.title)); + toast_overlay.add_toast (toast); + + return; + } + var system = Application.systems.get (game.system); if (system == null) { @@ -315,31 +322,26 @@ namespace RetroPlus { download_popover.add_download (game, media, system); var toast = new Adw.Toast (_("%s download queued").printf (game.title)); - toast_overlay.add_toast (toast); } void on_download_finished (Models.Game game) { var toast = new Adw.Toast (_("%s finished downloading").printf (game.title)); - toast_overlay.add_toast (toast); } void on_download_cancelled (Models.Game game) { var toast = new Adw.Toast (_("%s download cancelled").printf (game.title)); - toast_overlay.add_toast (toast); } void on_download_error (Models.Game game) { var toast = new Adw.Toast (_("%s could not download due to an error").printf (game.title)); - toast_overlay.add_toast (toast); } void on_download_file_exists (Models.Game game) { var toast = new Adw.Toast (_("%s is already downloaded").printf (game.title)); - toast_overlay.add_toast (toast); } } diff --git a/src/meson.build b/src/meson.build index 1dd7da7..4ab695c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -24,7 +24,8 @@ sources = [ 'utils/web.vala', 'utils/filesystem.vala', - 'utils/parser.vala', + + 'utils/parsers/vimms-lair-parser.vala', 'models/game.vala', 'models/system.vala', @@ -46,6 +47,7 @@ deps = [ dependency('libsoup-3.0'), dependency('json-glib-1.0'), dependency('gee-0.8'), + dependency('libxml-2.0') ] executable( diff --git a/src/models/game.vala b/src/models/game.vala index 9f45da9..64d8f40 100644 --- a/src/models/game.vala +++ b/src/models/game.vala @@ -5,6 +5,7 @@ namespace RetroPlus.Models { public string system { get; set; } public List regions; public int max_players { get; set; } + public bool removed { get; set; } public bool has_max_players { get { return max_players > 0; @@ -119,7 +120,7 @@ namespace RetroPlus.Models { var game = this; - if (!Utils.Parser.parse_game_request (res, ref game)) { + if (!Utils.VimmsLairParser.parse_game_request (res, ref game)) { return output = false; } diff --git a/src/models/system.vala b/src/models/system.vala index 9db25d6..fa4ed75 100644 --- a/src/models/system.vala +++ b/src/models/system.vala @@ -5,22 +5,9 @@ namespace RetroPlus.Models { public bool handheld { get; private set; } public uint year { get; private set; } public string download_directory_setting_name { get; private set; } - public bool extra_info_loaded { get; set; } - public uint media_count { get; set; } - public uint media_total { get; set; } - public string media_last_synchronization_date { get; set; } - public unowned List monthly_top_ten_downloads_list { get; set; } - public unowned List overall_rating_list { get; set; } - public unowned List graphics_list { get; set; } - public unowned List sound_list { get; set; } - public unowned List gameplay_list { get; set; } public string get_url() { - return "https://vimm.net/vault/" + id; - } - - public float get_media_pourcentage() { - return (media_count / (float) media_total) * 100; + return @"https://vimm.net/vault/$id"; } public System(string id, string title, bool handheld, uint year, string download_directory_setting_name) { @@ -31,25 +18,6 @@ namespace RetroPlus.Models { this.download_directory_setting_name = download_directory_setting_name; } - public bool load_extra_info(bool force_load = false) { - // - if (extra_info_loaded && !force_load)return true; - - // - var res = ""; - var res_valid = Utils.Web.get_request(get_url(), ref res); - - // - if (!res_valid)return false; - - // - var temp_system = this; - var parsed = Utils.Parser.parse_system_request(res, ref temp_system); - - // - return extra_info_loaded = !parsed; - } - public class get_games_by_title_result { public List games; public bool request_error; @@ -72,7 +40,7 @@ namespace RetroPlus.Models { result.request_error = true; } else { // - var parsing_valid = Utils.Parser.parse_search_request(res, ref result.games); + var parsing_valid = Utils.VimmsLairParser.parse_search_request(res, ref result.games); if (!parsing_valid) { result.parsing_error = true; } diff --git a/src/utils/parser.vala b/src/utils/parser.vala deleted file mode 100644 index 9ccf07b..0000000 --- a/src/utils/parser.vala +++ /dev/null @@ -1,811 +0,0 @@ -namespace RetroPlus.Utils { - public class Parser { - public static bool parse_markup (string text, out string parsed_text) { - try { - Pango.AttrList attr_list; - unichar accel_char; - Pango.parse_markup (text, text.length, 0, out attr_list, out parsed_text, out accel_char); - return true; - } catch { - return false; - } - } - - public static bool parse_search_request (string res, ref List games) { - if (res.length == 0)return false; - if (res.contains ("No matches found."))return true; - - var start_text = ""; - var start = res.index_of (start_text, res.index_of (start_text) + start_text.length); - - var end_text = ""; - var end = res.index_of (end_text, start); - - var temp_list = res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - - var offset = res.contains (">System") ? 1 : 0; - - while (temp_list.length != 0) { - temp_list = temp_list.strip (); - - var raw_id = ""; - var raw_title = ""; - var raw_manual_id = ""; - var extras = new List (); - var regions = new List (); - Models.Media media = null; - var system = ""; - - start_text = ""; - start = temp_list.index_of (start_text); - - end_text = ""; - end = temp_list.index_of (end_text); - - var temp_game = temp_list.substring (start + start_text.length, end - (start + start_text.length)); - - temp_list = temp_list.substring (temp_game.length + start_text.length + end_text.length); - - if (temp_game.contains (""))break; - - for (int i = 0; i < 5; i++) { - start_text = "", ""); - - // - var extra_start = temp_line.index_of ("redBorder", end); - - if (extra_start != -1 && !temp_line.contains ("Download unavailable")) { - // - start_text = "title=\""; - start = temp_line.index_of (start_text, extra_start); - - end_text = "\">"; - end = temp_line.index_of (end_text, start); - - var extra_title = temp_line.substring (start + start_text.length, end - (start + start_text.length)); - - // - start_text = ">"; - start = temp_line.index_of (start_text, end); - - end_text = ""; - end = temp_line.index_of (end_text, start); - - var extra_short_title = temp_line.substring (start + start_text.length, end - (start + start_text.length)); - - // - var extra = new Models.Extra (extra_title, extra_short_title); - extras.append (extra); - - // - extra_start = temp_line.index_of ("redBorder", end); - - if (extra_start != -1) { - // - start_text = "title=\""; - start = temp_line.index_of (start_text, extra_start); - - - end_text = "\">"; - end = temp_line.index_of (end_text, start); - - extra_title = temp_line.substring (start + start_text.length, end - (start + start_text.length)); - - // - start_text = ">"; - start = temp_line.index_of (start_text, extra_start); - - - end_text = ""; - end = temp_line.index_of (end_text, start); - - extra_short_title = temp_line.substring (start + start_text.length, end - (start + start_text.length)); - - // - extra = new Models.Extra (extra_title, extra_short_title); - extras.append (extra); - } - } - - // - if (temp_line.contains ("/images/manual_1.gif")) { - start_text = "manual/"; - start = temp_line.index_of (start_text, end); - - end_text = "\">"; - end = temp_line.index_of (end_text, start); - - raw_manual_id = temp_line.substring (start + start_text.length, end - (start + start_text.length)); - } - } - - // - if (i == 1 + offset) { - // - end = 0; - - // - if (!temp_line.contains (">-")) { - while (true) { - // - start_text = "flags/"; - start = temp_line.index_of (start_text, end); - - end_text = "\" class"; - end = temp_line.index_of (end_text, start); - - var region_flag_filename = temp_line.substring (start + start_text.length, end - (start + start_text.length)); - - // - start_text = "title=\""; - start = temp_line.index_of (start_text, end); - - end_text = "\" style"; - end = temp_line.index_of (end_text, start); - - var region_title = temp_line.substring (start + start_text.length, end - (start + start_text.length)); - - // - var region = new Models.Region (region_title, region_flag_filename); - - // - regions.append (region); - - // - if (temp_line.index_of ("title", end) == -1) break; - } - } - } - - // - if (i == 2 + offset) { - start_text = "\">"; - start = temp_line.index_of (start_text); - - end_text = "<"; - end = temp_line.index_of (end_text, start); - - var raw_version = temp_line.substring (start + start_text.length, end - (start + start_text.length)); - - double version; - if (!double.try_parse (raw_version, out version)) { - warning (@"Unable to parse the version ($raw_version)"); - return false; - } - - media = new Models.Media (version); - } - } - - int id; - var id_parsed = int.try_parse (raw_id, out id); - if (!id_parsed) { - warning (@"Unable to parse the id ($raw_id)"); - return false; - } - - string title; - var title_parsed = parse_markup(raw_title, out title); - if (!title_parsed) { - warning (@"Unable to parse the title ($raw_title)"); - return false; - } - - int manual_id; - var manual_parsed = int.try_parse (raw_manual_id, out manual_id); - if (!manual_parsed) { - warning (@"Unable to parse the manual id ($raw_manual_id)"); - return false; - } - - if (media == null) { - warning (@"Unable to parse the media"); - return false; - } - - var game = new Models.Game.from_search (id, system, title, manual_id, extras, regions, media); - games.append (game); - } - - return true; - } - - public static bool parse_game_request (string res, ref Models.Game game) { - // - var missing = res.contains ("This game is not currently in The Vault."); - - game.missing = missing; - - if (missing) { - warning (@"Unable to parse the game " + game.title + " since it's not currently in Vimm vault"); - return false; - } - - // - var start_text = "sectionTitle"; - var start = res.last_index_of (start_text) + 2; - - var end_text = ""; - var end = res.index_of (end_text, start); - - var system = res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - - game.system = system; - - // - start_text = "Players"; - start = res.index_of (start_text, start) + 59; - - end_text = ""; - end = res.index_of (end_text, start); - - var raw_max_players = res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - raw_max_players = raw_max_players.strip (); - - if (raw_max_players.contains ("Simultaneous")) { - raw_max_players = raw_max_players.split (" ")[0]; - game.simultaneous = true; - } - - if (raw_max_players.contains ("?")) { - game.max_players = -1; - } else { - int max_players; - if (!int.try_parse (raw_max_players, out max_players)) { - warning (@"Unable to parse the max players ($raw_max_players)"); - return false; - } - - game.max_players = max_players; - } - - // - start_text = "Year"; - start = res.index_of (start_text, start) + 38; - - end_text = ""; - end = res.index_of (end_text, start); - - var raw_year = res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - - if (raw_year.contains ("?")) { - game.year = -1; - } else { - int year; - if (!int.try_parse (raw_year, out year)) { - warning (@"Unable to parse the year ($raw_year)"); - return false; - } - - game.year = year; - } - - // - start_text = "Publisher"; - start = res.index_of (start_text, start) + 18; - - end_text = ""; - end = res.index_of (end_text, start) - 5; - - var publisher = start - 18 == -1 ? "" : res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - - if (publisher.length > 0) { - game.publisher = publisher; - } - - // - start_text = "Serial #"; - start = res.index_of (start_text, start) + 18; - if (res.contains ("id=\"serials\">"))start += 13; - - end_text = ""; - end = res.index_of (end_text, start) - 5; - - var serial = start - 18 == -1 ? "" : res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - serial = serial.replace ("
", " "); - - if (serial.length > 0) { - game.serial = serial; - } - - // - if (game.rated = !res.contains ("Rating")) { - // - start_text = "Graphics"; - start = res.index_of (start_text, start) + 18; - - end_text = ""; - end = res.index_of (end_text, start) - 5; - - var raw_graphics_rating = res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - - double graphics_rating; - if (!double.try_parse (raw_graphics_rating, out graphics_rating)) { - warning (@"Unable to parse the graphics rating ($raw_graphics_rating)"); - return false; - } - - game.graphics_rating = graphics_rating; - - // - start_text = "Sound"; - start = res.index_of (start_text, start) + 18; - - end_text = ""; - end = res.index_of (end_text, start) - 5; - - var raw_sound_rating = res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - - double sound_rating; - if (!double.try_parse (raw_sound_rating, out sound_rating)) { - warning (@"Unable to parse the sound rating ($raw_sound_rating)"); - return false; - } - - game.sound_rating = sound_rating; - - // - start_text = "Gameplay"; - start = res.index_of (start_text, start) + 18; - - end_text = ""; - end = res.index_of (end_text, start) - 5; - - var raw_gameplay_rating = res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - - double gameplay_rating; - if (!double.try_parse (raw_gameplay_rating, out gameplay_rating)) { - warning (@"Unable to parse the gameplay rating ($raw_gameplay_rating)"); - return false; - } - - game.gameplay_rating = gameplay_rating; - - // - start_text = "Overall"; - start = res.index_of (start_text, start) + 18; - - end_text = " 0) { - game.medias.remove (game.medias.nth_data (0)); - } - - for (var i = 0; i < medias_text_split.length; i++) { - // - if (i == medias_text_split.length - 1) break; - - // - var line = medias_text_split[i]; - - // - start_text = "ID\":"; - start = line.index_of (start_text, 0); - - end_text = ","; - end = line.index_of (end_text, start); - - var raw_id = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); - - int id; - if (!int.try_parse (raw_id, out id)) { - warning (@"Unable to parse the id ($raw_id)"); - return false; - } - - // - start_text = "Version\":\""; - start = line.index_of (start_text, 0); - - end_text = "\","; - end = line.index_of (end_text, start); - - var raw_version = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); - - double version; - if (!double.try_parse (raw_version, out version)) { - warning (@"Unable to parse the version ($raw_version)"); - return false; - } - - // - start_text = "Zipped\":\""; - start = line.index_of (start_text, end); - - end_text = "\","; - end = line.index_of (end_text, start); - - var raw_download_size = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); - - double download_size; - if (!double.try_parse (raw_download_size, out download_size)) { - warning (@"Unable to parse the download size ($raw_download_size)"); - return false; - } - - // - string crc = null; - string md5 = null; - string sha1 = null; - - // - start_text = "GoodHash\":\""; - start = line.index_of (start_text, end); - - if (start != -1) { - end_text = "\","; - end = line.index_of (end_text, start); - - crc = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); - } - - // - start_text = "GoodMd5\":\""; - start = line.index_of (start_text, end); - - if (start != -1) { - end_text = "\","; - end = line.index_of (end_text, start); - - md5 = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); - } - - // - start_text = "GoodSha1\":\""; - start = line.index_of (start_text, end); - - if (start != -1) { - end_text = "\"}"; - end = line.index_of (end_text, start); - - sha1 = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); - } - - // - var media = new Models.Media.extra (id, version, crc, md5, sha1, download_size); - game.medias.append (media); - } - - // - start_text = "//download"; - start = res.index_of (start_text, 0); - - end_text = "\" method="; - end = res.index_of (end_text, start); - - game.download_server = "https:" + res.substring (start, end - start); - - // - return true; - } - - public static bool parse_system_request (string res, ref Models.System console) { - // - var start_text = "Have "; - var start = res.index_of (start_text); - - var end_text = " of "; - var end = res.index_of (end_text); - - var media_count = res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - - // - start_text = " of "; - start = end; - - end_text = "media"; - end = res.index_of (end_text, start); - - var media_total = res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - - // - start_text = "dat: "; - start = res.index_of (start_text, end); - - end_text = ""; - end = res.index_of (end_text, start); - - var media_last_synchronization_date = res.substring (start + start_text.length, res.length - start - start_text.length - (res.length - end)); - - // - console.media_count = int.parse (media_count); - console.media_total = int.parse (media_total); - console.media_last_synchronization_date = media_last_synchronization_date; - - // - start_text = ""); - - for (int i = 1; i < overall_rating_rows.length; i++) { - // - start_text = "href=\""; - start = overall_rating_rows[i].index_of (start_text); - - end_text = "\">"; - end = overall_rating_rows[i].index_of (end_text); - - var id = overall_rating_rows[i].substring (start + start_text.length, overall_rating_rows[i].length - start - start_text.length - (overall_rating_rows[i].length - end)); - id = id.replace ("/vault/", ""); - - // - start_text = end_text; - start = end; - - end_text = "<"; - end = overall_rating_rows[i].index_of (end_text, start); - - var title = overall_rating_rows[i].substring (start + start_text.length, overall_rating_rows[i].length - start - start_text.length - (overall_rating_rows[i].length - end)); - parse_markup(title, out title); - - // - var game = new Models.Game (int.parse (id), title); - console.overall_rating_list.append (game); - } - - // - start_text = "Graphics"; - start = res.index_of (start_text, end); - - end_text = ""; - end = res.index_of (end_text, start); - - var graphics = res.substring (start, res.length - start - (res.length - end)); - var graphics_rows = graphics.split (""); - - for (int i = 1; i < graphics_rows.length; i++) { - // - start_text = "href=\""; - start = graphics_rows[i].index_of (start_text); - - end_text = "\">"; - end = graphics_rows[i].index_of (end_text); - - var id = graphics_rows[i].substring (start + start_text.length, graphics_rows[i].length - start - start_text.length - (graphics_rows[i].length - end)); - id = id.replace ("/vault/", ""); - - // - start_text = end_text; - start = end; - - end_text = "<"; - end = graphics_rows[i].index_of (end_text, start); - - var title = graphics_rows[i].substring (start + start_text.length, graphics_rows[i].length - start - start_text.length - (graphics_rows[i].length - end)); - parse_markup(title, out title); - - // - var game = new Models.Game (int.parse (id), title); - console.graphics_list.append (game); - } - - // - start_text = "Sound"; - start = res.index_of (start_text, end); - - end_text = ""; - end = res.index_of (end_text, start); - - var sound = res.substring (start, res.length - start - (res.length - end)); - var sound_rows = sound.split (""); - - for (int i = 1; i < sound_rows.length; i++) { - // - start_text = "href=\""; - start = sound_rows[i].index_of (start_text); - - end_text = "\">"; - end = sound_rows[i].index_of (end_text); - - var id = sound_rows[i].substring (start + start_text.length, sound_rows[i].length - start - start_text.length - (sound_rows[i].length - end)); - id = id.replace ("/vault/", ""); - - // - start_text = end_text; - start = end; - - end_text = "<"; - end = sound_rows[i].index_of (end_text, start); - - var title = sound_rows[i].substring (start + start_text.length, sound_rows[i].length - start - start_text.length - (sound_rows[i].length - end)); - parse_markup(title, out title); - - // - var game = new Models.Game (int.parse (id), title); - console.sound_list.append (game); - } - - // - start_text = "Gameplay"; - start = res.index_of (start_text, end); - - end_text = ""; - end = res.index_of (end_text, start); - - var gameplay = res.substring (start, res.length - start - (res.length - end)); - var gameplay_rows = gameplay.split (""); - - for (int i = 1; i < gameplay_rows.length; i++) { - // - start_text = "href=\""; - start = gameplay_rows[i].index_of (start_text); - - end_text = "\">"; - end = gameplay_rows[i].index_of (end_text); - - var id = gameplay_rows[i].substring (start + start_text.length, gameplay_rows[i].length - start - start_text.length - (gameplay_rows[i].length - end)); - id = id.replace ("/vault/", ""); - - // - start_text = end_text; - start = end; - - end_text = "<"; - end = gameplay_rows[i].index_of (end_text, start); - - var title = gameplay_rows[i].substring (start + start_text.length, gameplay_rows[i].length - start - start_text.length - (gameplay_rows[i].length - end)); - parse_markup(title, out title); - - // - var game = new Models.Game (int.parse (id), title); - console.gameplay_list.append (game); - } - - // - return true; - } - } -} \ No newline at end of file diff --git a/src/utils/parsers/vimms-lair-parser.vala b/src/utils/parsers/vimms-lair-parser.vala new file mode 100644 index 0000000..daaabec --- /dev/null +++ b/src/utils/parsers/vimms-lair-parser.vala @@ -0,0 +1,407 @@ +namespace RetroPlus.Utils { + public class VimmsLairParser { + public static bool parse_markup (string text, out string parsed_text) { + try { + Pango.AttrList attr_list; + unichar accel_char; + Pango.parse_markup (text, text.length, 0, out attr_list, out parsed_text, out accel_char); + return true; + } catch { + return false; + } + } + + static void process_node_by_name(string? name, Xml.Node* node, ref List node_list) { + if (node->name == name || name == null) { + node_list.append (node); + } + + for (Xml.Node* child = node->children; child != null; child = child->next) { + process_node_by_name(name, child, ref node_list); + } + } + + public static bool parse_search_request (string html, ref List games) { + if (html.length == 0)return false; + if (html.contains ("No matches found."))return true; + + var start_text = ""; + var start = html.index_of (start_text, html.index_of (start_text) + start_text.length); + + var end_text = ""; + var end = html.index_of (end_text, start); + + var temp_list = html.substring (start + start_text.length, html.length - start - start_text.length - (html.length - end)); + + var context = new Html.ParserCtxt(); + var doc = context.read_memory (temp_list.to_utf8 (), temp_list.length, "", "UTF-8", Html.ParserOption.NOERROR); + + if (doc == null) { + warning("Error parsing HTML"); + return false; + } + + var root_node = doc->get_root_element(); + + var game_nodes = new List(); + process_node_by_name("tr", root_node, ref game_nodes); + + foreach(var node in game_nodes) { + var td_nodes = new List(); + var a_nodes = new List(); + var img_nodes = new List(); + var b_nodes = new List(); + + process_node_by_name("td", node, ref td_nodes); + process_node_by_name("a", node, ref a_nodes); + process_node_by_name("img", node, ref img_nodes); + process_node_by_name("b", node, ref b_nodes); + + // Process System + var system_name = ""; + foreach (var td_node in td_nodes) { + var system = Application.systems.values.first_match ((system) => { + if (system.id == td_node->get_content()) return true; + return false; + }); + + if (system != null) { + system_name = system.id; + break; + } + } + if (system_name == "") { + warning (@"Failed finding the system name of a game"); + continue; + } + + // Process ID + var id = -1; + var raw_id = ""; + Xml.Node* id_node = null; + foreach (var a_node in a_nodes){ + raw_id = a_node->get_prop ("href"); + if (raw_id?.contains ("/vault/")) { + raw_id = raw_id.replace ("/vault/", ""); + if (int.try_parse (raw_id, out id)) { + id_node = a_node; + } + break; + } + } + if (id == -1) { + warning (@"Failed parsing the following id: $raw_id"); + continue; + } + + // Process Name + if (id_node == null) { + warning (@"Failed finding the name of a game"); + continue; + } + var title = id_node->get_content (); + + // Process Manual ID + var manual_id = -1; + var raw_manual_id = ""; + foreach (var a_node in a_nodes){ + raw_manual_id = a_node->get_prop ("href"); + if (raw_manual_id?.contains ("/manual/")) { + raw_manual_id = raw_manual_id.replace ("/manual/", ""); + int.try_parse (raw_manual_id, out manual_id); + break; + } + } + + // Process Extra + var extra_list = new GLib.List(); + var extra_title = ""; + var extra_short_title = ""; + Models.Extra extra = null; + foreach (var b_node in b_nodes) { + extra_title = b_node->get_prop ("title"); + extra_short_title = b_node->get_content(); + extra = new Models.Extra (extra_title, extra_short_title); + extra_list.append (extra); + } + + // Process Media + var version = -1.0d; + Models.Media media = null; + foreach (var td_node in td_nodes) { + if (double.try_parse (td_node->get_content (), out version)) { + media = new Models.Media (version); + break; + } + } + if (media == null) { + warning (@"Failed finding the version of a game"); + continue; + } + + // Process Region + var region_list = new GLib.List(); + var region_title = ""; + var flag_filename = ""; + Models.Region region = null; + foreach (var img_node in img_nodes) { + flag_filename = img_node->get_prop ("src"); + if (flag_filename?.contains ("/flags/")){ + flag_filename = flag_filename.replace ("/images/flags/", ""); + region_title = img_node->get_prop ("title"); + region = new Models.Region (region_title, flag_filename); + region_list.append (region); + } + } + + // Create game + var game = new Models.Game.from_search (id, system_name, title, manual_id, extra_list, region_list, media); + + // Append the game to the game list + games.append (game); + } + + free (doc); + + return true; + } + + public static bool parse_game_request (string html, ref Models.Game game) { + if (game.missing = html.contains ("This game is not currently in The Vault.")) { + return true; + } + + var context = new Html.ParserCtxt(); + var doc = context.read_memory (html.to_utf8 (), html.length, "", "UTF-8", Html.ParserOption.NOERROR); + + if (doc == null) { + warning("Error parsing HTML"); + return false; + } + + var root_node = doc->get_root_element(); + + var td_nodes = new List(); + process_node_by_name("td", root_node, ref td_nodes); + + Xml.Node* data_node = null; + foreach (var td_node in td_nodes){ + if (td_node->get_content () == "Region") { + data_node = td_node->parent->parent; + break; + } + } + if (data_node == null) { + return false; + } + + game.rated = !html.contains ("Rating"); + + var tr_nodes = new List(); + process_node_by_name("tr", data_node, ref tr_nodes); + + foreach(var tr_node in tr_nodes) { + var nodes = new List(); + process_node_by_name(null, tr_node, ref nodes); + + var content = tr_node->get_content ().strip(); + + if (content.contains ("Download unavailable by request of")) { + game.removed = true; + } + + if (tr_node->get_prop ("id") == "row-date" && game.last_verification_date == null) { + foreach (var node in nodes) { + if (node->get_prop ("id") == "data-date") { + game.last_verification_date = node->get_content (); + } + } + } + + if (tr_node->last_element_child ()->get_prop ("id") == "serials") { + game.serial = tr_node->last_element_child ()->get_content (); + } + + if (content.contains ("Publisher")) { + game.publisher = content.replace ("Publisher", ""); + } + + if (content.contains ("Year")) { + var raw_year = content.replace ("Year", ""); + var year = -1; + if (int.try_parse (raw_year, out year)) { + game.year = year; + } + } + + if (content.contains ("Players")) { + var raw_max_players = content.replace ("Players", "").strip(); + + if (game.simultaneous = raw_max_players.contains ("Simultaneous")) { + raw_max_players = raw_max_players.replace ("Simultaneous", "").strip(); + } + + var max_players = -1; + if (int.try_parse (raw_max_players, out max_players)) { + game.max_players = max_players; + } + } + + if (game.rated) { + if (game.rated && content.contains ("Graphics")) { + var rating = -1.0d; + if (double.try_parse (content.replace ("Graphics", ""), out rating)) { + game.graphics_rating = rating; + } + } + + if (game.rated && content.contains ("Sound")) { + var rating = -1.0d; + if (double.try_parse (content.replace ("Sound", ""), out rating)) { + game.sound_rating = rating; + } + } + + if (game.rated && content.contains ("Gameplay")) { + var rating = -1.0d; + if (double.try_parse (content.replace ("Gameplay", ""), out rating)) { + game.gameplay_rating = rating; + } + } + + if (game.rated && content.contains ("Overall")) { + content = content.split (")")[0]; + + var list = content.split("("); + + var rating = -1.0d; + if (double.try_parse (list[0].replace ("Overall", "").replace(" ", "").strip(), out rating)) { + game.overall_rating = rating; + } + + var votes = -1; + if (int.try_parse (list[1].split("v")[0].replace(" ", "").strip(), out votes)) { + game.total_votes = votes; + } + } + } + } + + // Find if the game support play online + game.support_play_online = html.contains ("Play Online"); + + // Find all the available versions + var start_text = "var allMedia = [];"; + var start = html.index_of (start_text); + + var end_text = "document.addEventListener"; + var end = html.index_of (end_text, start); + + var medias_text = html.substring (start + start_text.length, html.length - start - start_text.length - (html.length - end)); + var medias_text_split = medias_text.split ("allMedia.push(media);"); + + while (game.medias.length () > 0) { + game.medias.remove (game.medias.nth_data (0)); + } + + for (var i = 0; i < medias_text_split.length; i++) { + // + if (i == medias_text_split.length - 1) break; + + // + var line = medias_text_split[i]; + + // + start_text = "ID\":"; + start = line.index_of (start_text, 0); + + end_text = ","; + end = line.index_of (end_text, start); + + var raw_id = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); + + int id; + if (!int.try_parse (raw_id, out id)) { + warning (@"Unable to parse the id ($raw_id)"); + return false; + } + + // + start_text = "Version\":\""; + start = line.index_of (start_text, 0); + + end_text = "\","; + end = line.index_of (end_text, start); + + var raw_version = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); + + double version; + if (!double.try_parse (raw_version, out version)) { + warning (@"Unable to parse the version ($raw_version)"); + return false; + } + + // + start_text = "Zipped\":\""; + start = line.index_of (start_text, end); + + end_text = "\","; + end = line.index_of (end_text, start); + + var raw_download_size = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); + + double download_size; + if (!double.try_parse (raw_download_size, out download_size)) { + warning (@"Unable to parse the download size ($raw_download_size)"); + return false; + } + + // + string crc = null; + string md5 = null; + string sha1 = null; + + // + start_text = "GoodHash\":\""; + start = line.index_of (start_text, end); + + if (start != -1) { + end_text = "\","; + end = line.index_of (end_text, start); + + crc = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); + } + + // + start_text = "GoodMd5\":\""; + start = line.index_of (start_text, end); + + if (start != -1) { + end_text = "\","; + end = line.index_of (end_text, start); + + md5 = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); + } + + // + start_text = "GoodSha1\":\""; + start = line.index_of (start_text, end); + + if (start != -1) { + end_text = "\"}"; + end = line.index_of (end_text, start); + + sha1 = line.substring (start + start_text.length, line.length - start - start_text.length - (line.length - end)); + } + + // + var media = new Models.Media.extra (id, version, crc, md5, sha1, download_size); + game.medias.append (media); + } + + // Find download server + + return true; + } + } +} \ No newline at end of file diff --git a/src/widgets/game-detail-dialog.vala b/src/widgets/game-detail-dialog.vala index a8c32e9..52d2d2b 100644 --- a/src/widgets/game-detail-dialog.vala +++ b/src/widgets/game-detail-dialog.vala @@ -225,6 +225,9 @@ namespace RetroPlus.Widgets { } Gtk.Box get_hash_box () { + var last_verification_date_label = new Gtk.Label (_("Verified") + ": " + game.last_verification_date); + last_verification_date_label.set_tooltip_text (game.last_verification_date); + crc_label = new Gtk.Label (_("CRC") + ": " + game.medias.nth_data (0).crc); crc_label.set_tooltip_text (game.medias.nth_data (0).crc); @@ -240,6 +243,7 @@ namespace RetroPlus.Widgets { var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 5); box.set_valign (Gtk.Align.CENTER); box.set_hexpand (true); + box.append (last_verification_date_label); box.append (crc_label); box.append (md5_label); box.append (sha1_label); @@ -248,4 +252,4 @@ namespace RetroPlus.Widgets { return box; } } -} +} \ No newline at end of file diff --git a/src/widgets/search-row.vala b/src/widgets/search-row.vala index 91fd387..68bd0e4 100644 --- a/src/widgets/search-row.vala +++ b/src/widgets/search-row.vala @@ -10,6 +10,7 @@ namespace RetroPlus.Widgets { construct { this.set_spacing (10); this.add_css_class ("p-10"); + this.set_size_request (0, 60); system_label = new Gtk.Label (null); system_label.set_halign (Gtk.Align.CENTER); @@ -38,7 +39,7 @@ namespace RetroPlus.Widgets { region_grid.set_row_homogeneous (true); region_grid.set_halign (Gtk.Align.CENTER); region_grid.set_valign (Gtk.Align.CENTER); - region_grid.set_size_request (50, 50); + region_grid.set_size_request (50, 40); for (var i = 1; i <= 4; i++) { bool pair = (i % 2) == 0; int row = pair ? 1 : 0; From 15cd2174a800169b9a90e7b5173d626d43b8ba74 Mon Sep 17 00:00:00 2001 From: Charles Malouin Date: Fri, 16 Feb 2024 21:04:37 -0500 Subject: [PATCH 2/2] Update translation files --- po/com.vysp3r.RetroPlus.pot | 44 ++++++++++++++++++++++--------------- po/tr.po | 44 ++++++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 36 deletions(-) diff --git a/po/com.vysp3r.RetroPlus.pot b/po/com.vysp3r.RetroPlus.pot index fe9f587..a00b2c2 100644 --- a/po/com.vysp3r.RetroPlus.pot +++ b/po/com.vysp3r.RetroPlus.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: com.vysp3r.RetroPlus\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-12 04:02+0300\n" +"POT-Creation-Date: 2024-02-16 21:04-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -58,15 +58,14 @@ msgstr "" msgid "translator-credits" msgstr "" -#: src/main-window.vala:33 +#: src/main-window.vala:34 msgid "Preferences" msgstr "" -#: src/main-window.vala:34 +#: src/main-window.vala:35 msgid "Keyboard Shortcuts" msgstr "" -#. Translators: Do not translate the application name #: src/main-window.vala:36 msgid "About RetroPlus" msgstr "" @@ -96,54 +95,59 @@ msgstr "" msgid "Version" msgstr "" -#: src/main-window.vala:111 +#: src/main-window.vala:119 msgid "" "Feels empty in here.\n" "Why not search for a game?" msgstr "" -#: src/main-window.vala:187 +#: src/main-window.vala:215 msgid "" "Can't reach the servers.\n" "Please report this on our GitHub if you think this is a bug." msgstr "" -#: src/main-window.vala:192 +#: src/main-window.vala:220 msgid "" "An unknown error occurred.\n" "Please report this on our GitHub." msgstr "" -#: src/main-window.vala:197 +#: src/main-window.vala:225 msgid "Nothing found, try searching again." msgstr "" -#: src/main-window.vala:270 +#: src/main-window.vala:291 #, c-format msgid "An error occured while opening %s" msgstr "" -#: src/main-window.vala:296 +#: src/main-window.vala:309 +#, c-format +msgid "%s is currently missing/unavailable" +msgstr "" + +#: src/main-window.vala:324 #, c-format msgid "%s download queued" msgstr "" -#: src/main-window.vala:302 +#: src/main-window.vala:329 #, c-format msgid "%s finished downloading" msgstr "" -#: src/main-window.vala:308 +#: src/main-window.vala:334 #, c-format msgid "%s download cancelled" msgstr "" -#: src/main-window.vala:314 +#: src/main-window.vala:339 #, c-format msgid "%s could not download due to an error" msgstr "" -#: src/main-window.vala:320 +#: src/main-window.vala:344 #, c-format msgid "%s is already downloaded" msgstr "" @@ -230,17 +234,17 @@ msgid "See manual" msgstr "" #: src/widgets/game-detail-dialog.vala:198 -#: src/widgets/game-detail-dialog.vala:228 +#: src/widgets/game-detail-dialog.vala:231 msgid "CRC" msgstr "" #: src/widgets/game-detail-dialog.vala:199 -#: src/widgets/game-detail-dialog.vala:231 +#: src/widgets/game-detail-dialog.vala:234 msgid "MD5" msgstr "" #: src/widgets/game-detail-dialog.vala:200 -#: src/widgets/game-detail-dialog.vala:235 +#: src/widgets/game-detail-dialog.vala:238 msgid "SHA1" msgstr "" @@ -268,10 +272,14 @@ msgstr "" msgid "vote" msgstr "" +#: src/widgets/game-detail-dialog.vala:228 +msgid "Verified" +msgstr "" + #: src/widgets/search-filter-box.vala:31 msgid "Source" msgstr "" -#: src/widgets/search-row.vala:40 +#: src/widgets/search-row.vala:27 msgid "Manual available" msgstr "" diff --git a/po/tr.po b/po/tr.po index 59fdc97..d350430 100644 --- a/po/tr.po +++ b/po/tr.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: com.vysp3r.RetroPlus\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-02-12 04:02+0300\n" +"POT-Creation-Date: 2024-02-16 21:04-0500\n" "PO-Revision-Date: 2024-02-12 04:02+0300\n" "Last-Translator: Sabri Ünal \n" "Language-Team: \n" @@ -62,15 +62,14 @@ msgstr "Özel teşekkürler" msgid "translator-credits" msgstr "Sabri Ünal " -#: src/main-window.vala:33 +#: src/main-window.vala:34 msgid "Preferences" msgstr "Tercihler" -#: src/main-window.vala:34 +#: src/main-window.vala:35 msgid "Keyboard Shortcuts" msgstr "Klavye Kısayolları" -#. Translators: Do not translate the application name #: src/main-window.vala:36 msgid "About RetroPlus" msgstr "RetroPlus Hakkında" @@ -100,7 +99,7 @@ msgstr "Bölge" msgid "Version" msgstr "Sürüm" -#: src/main-window.vala:111 +#: src/main-window.vala:119 msgid "" "Feels empty in here.\n" "Why not search for a game?" @@ -108,7 +107,7 @@ msgstr "" "Burası boş gibi.\n" "Neden bir oyun aramıyorsunuz?" -#: src/main-window.vala:187 +#: src/main-window.vala:215 msgid "" "Can't reach the servers.\n" "Please report this on our GitHub if you think this is a bug." @@ -116,7 +115,7 @@ msgstr "" "Sunuculara ulaşılamıyor.\n" "Bunun bir hata olduğunu düşünüyorsanız lütfen GitHub üstünden bildirin." -#: src/main-window.vala:192 +#: src/main-window.vala:220 msgid "" "An unknown error occurred.\n" "Please report this on our GitHub." @@ -124,36 +123,41 @@ msgstr "" "Bilinmeyen bir hata oluştu.\n" "Lütfen bunu GitHub üstünden bildirin." -#: src/main-window.vala:197 +#: src/main-window.vala:225 msgid "Nothing found, try searching again." msgstr "Hiçbir şey bulunamadı, tekrar aramayı deneyin." -#: src/main-window.vala:270 +#: src/main-window.vala:291 #, c-format msgid "An error occured while opening %s" msgstr "%s açılırken hata oluştu" -#: src/main-window.vala:296 +#: src/main-window.vala:309 +#, c-format +msgid "%s is currently missing/unavailable" +msgstr "" + +#: src/main-window.vala:324 #, c-format msgid "%s download queued" msgstr "%s indirme sırasına eklendi" -#: src/main-window.vala:302 +#: src/main-window.vala:329 #, c-format msgid "%s finished downloading" msgstr "%s indirildi" -#: src/main-window.vala:308 +#: src/main-window.vala:334 #, c-format msgid "%s download cancelled" msgstr "%s indirmesi iptal edildi" -#: src/main-window.vala:314 +#: src/main-window.vala:339 #, c-format msgid "%s could not download due to an error" msgstr "%s hata nedeniyle indirilemedi" -#: src/main-window.vala:320 +#: src/main-window.vala:344 #, c-format msgid "%s is already downloaded" msgstr "%s zaten indirilmiş" @@ -240,17 +244,17 @@ msgid "See manual" msgstr "Kılavuza bak" #: src/widgets/game-detail-dialog.vala:198 -#: src/widgets/game-detail-dialog.vala:228 +#: src/widgets/game-detail-dialog.vala:231 msgid "CRC" msgstr "CRC" #: src/widgets/game-detail-dialog.vala:199 -#: src/widgets/game-detail-dialog.vala:231 +#: src/widgets/game-detail-dialog.vala:234 msgid "MD5" msgstr "MD5" #: src/widgets/game-detail-dialog.vala:200 -#: src/widgets/game-detail-dialog.vala:235 +#: src/widgets/game-detail-dialog.vala:238 msgid "SHA1" msgstr "SHA1" @@ -278,11 +282,15 @@ msgstr "oy" msgid "vote" msgstr "oy" +#: src/widgets/game-detail-dialog.vala:228 +msgid "Verified" +msgstr "" + #: src/widgets/search-filter-box.vala:31 msgid "Source" msgstr "Kaynak" -#: src/widgets/search-row.vala:40 +#: src/widgets/search-row.vala:27 msgid "Manual available" msgstr "Kılavuz var"