From 26937ccc91eda2a259f7ebf78eded4b486dc7b71 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Sun, 9 Jun 2019 09:51:48 +0300 Subject: [PATCH 01/24] platform: Add get_uid_prefix () --- plugins/desktop/src/desktop-plugin.vala | 3 ++- plugins/dreamcast/src/dreamcast-plugin.vala | 3 ++- plugins/game-cube/src/game-cube-plugin.vala | 3 ++- plugins/libretro/src/libretro-plugin.vala | 3 ++- plugins/love/src/love-plugin.vala | 6 +++--- plugins/ms-dos/src/ms-dos-plugin.vala | 6 +++--- plugins/nintendo-ds/src/nintendo-ds-plugin.vala | 6 +++--- plugins/playstation/src/playstation-plugin.vala | 3 ++- plugins/sega-cd/src/sega-cd-plugin.vala | 9 +++++---- plugins/sega-saturn/src/sega-saturn-plugin.vala | 3 ++- plugins/steam/src/steam-plugin.vala | 3 ++- plugins/turbografx-cd/src/turbografx-cd-plugin.vala | 6 +++--- plugins/virtual-boy/src/virtual-boy-plugin.vala | 6 +++--- plugins/wii/src/wii-plugin.vala | 3 ++- src/core/platform.vala | 2 ++ src/dummy/dummy-platform.vala | 4 ++++ src/generic/generic-platform.vala | 8 +++++++- src/retro/retro-platform.vala | 8 +++++++- src/ui/application.vala | 2 +- 19 files changed, 57 insertions(+), 30 deletions(-) diff --git a/plugins/desktop/src/desktop-plugin.vala b/plugins/desktop/src/desktop-plugin.vala index e7967f5d..1b03dcd4 100644 --- a/plugins/desktop/src/desktop-plugin.vala +++ b/plugins/desktop/src/desktop-plugin.vala @@ -4,11 +4,12 @@ private class Games.DesktopPlugin : Object, Plugin { private const string MIME_TYPE = "application/x-desktop"; private const string PLATFORM_ID = "Desktop"; private const string PLATFORM_NAME = _("Desktop"); + private const string PLATFORM_UID_PREFIX = "desktop"; private static Platform platform; static construct { - platform = new GenericPlatform (PLATFORM_ID, PLATFORM_NAME); + platform = new GenericPlatform (PLATFORM_ID, PLATFORM_NAME, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { diff --git a/plugins/dreamcast/src/dreamcast-plugin.vala b/plugins/dreamcast/src/dreamcast-plugin.vala index bcf4c2fe..93a89948 100644 --- a/plugins/dreamcast/src/dreamcast-plugin.vala +++ b/plugins/dreamcast/src/dreamcast-plugin.vala @@ -4,11 +4,12 @@ private class Games.DreamcastPlugin : Object, Plugin { private const string MIME_TYPE = "application/x-dc-rom"; private const string PLATFORM_ID = "Dreamcast"; private const string PLATFORM_NAME = _("Dreamcast"); + private const string PLATFORM_UID_PREFIX = "dreamcast"; private static RetroPlatform platform; static construct { - platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }); + platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { diff --git a/plugins/game-cube/src/game-cube-plugin.vala b/plugins/game-cube/src/game-cube-plugin.vala index eea79684..8bc4411e 100644 --- a/plugins/game-cube/src/game-cube-plugin.vala +++ b/plugins/game-cube/src/game-cube-plugin.vala @@ -4,11 +4,12 @@ private class Games.GameCubePlugin : Object, Plugin { private const string MIME_TYPE = "application/x-gamecube-rom"; private const string PLATFORM_ID = "GameCube"; private const string PLATFORM_NAME = _("Nintendo GameCube"); + private const string PLATFORM_UID_PREFIX = "game-cube"; private static RetroPlatform platform; static construct { - platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }); + platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { diff --git a/plugins/libretro/src/libretro-plugin.vala b/plugins/libretro/src/libretro-plugin.vala index 75ec64b9..84008eb4 100644 --- a/plugins/libretro/src/libretro-plugin.vala +++ b/plugins/libretro/src/libretro-plugin.vala @@ -4,11 +4,12 @@ private class Games.LibretroPlugin : Object, Plugin { private const string LIBRETRO_FILE_SCHEME = "libretro+file"; private const string PLATFORM_ID = "Libretro"; private const string PLATFORM_NAME = _("Libretro"); + private const string PLATFORM_UID_PREFIX = "libretro"; private static Platform platform; static construct { - platform = new GenericPlatform (PLATFORM_ID, PLATFORM_NAME); + platform = new GenericPlatform (PLATFORM_ID, PLATFORM_NAME, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { diff --git a/plugins/love/src/love-plugin.vala b/plugins/love/src/love-plugin.vala index add7c237..34e51038 100644 --- a/plugins/love/src/love-plugin.vala +++ b/plugins/love/src/love-plugin.vala @@ -1,15 +1,15 @@ // This file is part of GNOME Games. License: GPL-3.0+. private class Games.LovePlugin : Object, Plugin { - private const string FINGERPRINT_PREFIX = "love"; private const string MIME_TYPE = "application/x-love-game"; private const string PLATFORM_ID = "LOVE"; private const string PLATFORM_NAME = _("LÖVE"); + private const string PLATFORM_UID_PREFIX = "love"; private static Platform platform; static construct { - platform = new GenericPlatform (PLATFORM_ID, PLATFORM_NAME); + platform = new GenericPlatform (PLATFORM_ID, PLATFORM_NAME, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { @@ -29,7 +29,7 @@ private class Games.LovePlugin : Object, Plugin { } private static Game game_for_uri (Uri uri) throws Error { - var uid = new FingerprintUid (uri, FINGERPRINT_PREFIX); + var uid = new FingerprintUid (uri, PLATFORM_UID_PREFIX); var package = new LovePackage (uri); var title = new LoveTitle (package); var icon = new LoveIcon (package); diff --git a/plugins/ms-dos/src/ms-dos-plugin.vala b/plugins/ms-dos/src/ms-dos-plugin.vala index 10fdd1a3..42ed8935 100644 --- a/plugins/ms-dos/src/ms-dos-plugin.vala +++ b/plugins/ms-dos/src/ms-dos-plugin.vala @@ -1,15 +1,15 @@ // This file is part of GNOME Games. License: GPL-3.0+. private class Games.MsDosPlugin : Object, Plugin { - private const string FINGERPRINT_PREFIX = "ms-dos"; private const string MIME_TYPE = "application/x-ms-dos-executable"; private const string PLATFORM_ID = "MSDOS"; private const string PLATFORM_NAME = _("MS-DOS"); + private const string PLATFORM_UID_PREFIX = "ms-dos"; private static RetroPlatform platform; static construct { - platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }); + platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { @@ -25,7 +25,7 @@ private class Games.MsDosPlugin : Object, Plugin { } private static Game game_for_uri (Uri uri) throws Error { - var uid = new FingerprintUid (uri, FINGERPRINT_PREFIX); + var uid = new FingerprintUid (uri, PLATFORM_UID_PREFIX); var title = new FilenameTitle (uri); var media = new GriloMedia (title, MIME_TYPE); var release_date = new GriloReleaseDate (media); diff --git a/plugins/nintendo-ds/src/nintendo-ds-plugin.vala b/plugins/nintendo-ds/src/nintendo-ds-plugin.vala index c397fda2..0ad5cd68 100644 --- a/plugins/nintendo-ds/src/nintendo-ds-plugin.vala +++ b/plugins/nintendo-ds/src/nintendo-ds-plugin.vala @@ -1,15 +1,15 @@ // This file is part of GNOME Games. License: GPL-3.0+. private class Games.NintendoDsPlugin : Object, Plugin { - private const string FINGERPRINT_PREFIX = "nintendo-ds"; private const string MIME_TYPE = "application/x-nintendo-ds-rom"; private const string PLATFORM_ID = "NintendoDS"; private const string PLATFORM_NAME = _("Nintendo DS"); + private const string PLATFORM_UID_PREFIX = "nintendo-ds"; private static RetroPlatform platform; static construct { - platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }); + platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { @@ -29,7 +29,7 @@ private class Games.NintendoDsPlugin : Object, Plugin { } private static Game game_for_uri (Uri uri) throws Error { - var uid = new FingerprintUid (uri, FINGERPRINT_PREFIX); + var uid = new FingerprintUid (uri, PLATFORM_UID_PREFIX); var title = new FilenameTitle (uri); var icon = new NintendoDsIcon (uri); var media = new GriloMedia (title, MIME_TYPE); diff --git a/plugins/playstation/src/playstation-plugin.vala b/plugins/playstation/src/playstation-plugin.vala index 967b4bef..7695de96 100644 --- a/plugins/playstation/src/playstation-plugin.vala +++ b/plugins/playstation/src/playstation-plugin.vala @@ -5,12 +5,13 @@ private class Games.PlayStation : Object, Plugin { private const string PHONY_MIME_TYPE = "application/x-playstation-rom"; private const string PLATFORM_ID = "PlayStation"; private const string PLATFORM_NAME = _("PlayStation"); + private const string PLATFORM_UID_PREFIX = "playstation"; private static RetroPlatform platform; static construct { string[] mime_types = { CUE_MIME_TYPE, PHONY_MIME_TYPE }; - platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, mime_types); + platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, mime_types, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { diff --git a/plugins/sega-cd/src/sega-cd-plugin.vala b/plugins/sega-cd/src/sega-cd-plugin.vala index ae376ff8..56869813 100644 --- a/plugins/sega-cd/src/sega-cd-plugin.vala +++ b/plugins/sega-cd/src/sega-cd-plugin.vala @@ -3,7 +3,8 @@ private class Games.SegaCDPlugin : Object, Plugin { private const string 32X_MIME_TYPE = "application/x-genesis-32x-rom"; - private const string SEGA_CD_PREFIX = "mega-cd"; + private const string SEGA_CD_UID_PREFIX = "mega-cd"; + private const string SEGA_CD_32X_UID_PREFIX = "mega-cd"; private const string CUE_MIME_TYPE = "application/x-cue"; private const string SEGA_CD_MIME_TYPE = "application/x-sega-cd-rom"; private const string SEGA_CD_PLATFORM_ID = "SegaCD"; @@ -19,8 +20,8 @@ private class Games.SegaCDPlugin : Object, Plugin { static construct { string[] mime_types = { CUE_MIME_TYPE, SEGA_CD_MIME_TYPE }; string[] mime_types_32x = { CUE_MIME_TYPE, SEGA_CD_MIME_TYPE, 32X_MIME_TYPE }; - platform_sega_cd = new RetroPlatform (SEGA_CD_PLATFORM_ID, SEGA_CD_PLATFORM_NAME, mime_types); - platform_sega_cd_32x = new RetroPlatform (SEGA_CD_32X_PLATFORM_ID, SEGA_CD_32X_PLATFORM_NAME, mime_types_32x); + platform_sega_cd = new RetroPlatform (SEGA_CD_PLATFORM_ID, SEGA_CD_PLATFORM_NAME, mime_types, SEGA_CD_UID_PREFIX); + platform_sega_cd_32x = new RetroPlatform (SEGA_CD_32X_PLATFORM_ID, SEGA_CD_32X_PLATFORM_NAME, mime_types_32x, SEGA_CD_32X_UID_PREFIX); } public Platform[] get_platforms () { @@ -73,7 +74,7 @@ private class Games.SegaCDPlugin : Object, Plugin { var bin_uri = new Uri (bin_file.get_uri ()); var header_offset = header.get_offset (); - var uid = new FingerprintUid.for_chunk (bin_uri, SEGA_CD_PREFIX, header_offset, SegaCDHeader.HEADER_LENGTH); + var uid = new FingerprintUid.for_chunk (bin_uri, SEGA_CD_UID_PREFIX, header_offset, SegaCDHeader.HEADER_LENGTH); var title = new FilenameTitle (uri); var media = new GriloMedia (title, SEGA_CD_MIME_TYPE); var cover = new CompositeCover ({ diff --git a/plugins/sega-saturn/src/sega-saturn-plugin.vala b/plugins/sega-saturn/src/sega-saturn-plugin.vala index 4637d2d2..1e0aac04 100644 --- a/plugins/sega-saturn/src/sega-saturn-plugin.vala +++ b/plugins/sega-saturn/src/sega-saturn-plugin.vala @@ -5,12 +5,13 @@ private class Games.SegaSaturnPlugin : Object, Plugin { private const string SEGA_SATURN_MIME_TYPE = "application/x-saturn-rom"; private const string PLATFORM_ID = "SegaSaturn"; private const string PLATFORM_NAME = _("Sega Saturn"); + private const string PLATFORM_UID_PREFIX = "sega-saturn"; private static RetroPlatform platform; static construct { string[] mime_types = { CUE_MIME_TYPE, SEGA_SATURN_MIME_TYPE }; - platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, mime_types); + platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, mime_types, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { diff --git a/plugins/steam/src/steam-plugin.vala b/plugins/steam/src/steam-plugin.vala index 0e60dd90..5ea51ecc 100644 --- a/plugins/steam/src/steam-plugin.vala +++ b/plugins/steam/src/steam-plugin.vala @@ -8,11 +8,12 @@ private class Games.SteamPlugin : Object, Plugin { private const string FLATPAK_STEAM_FILE_SCHEME = "flatpak+steam+file"; private const string PLATFORM_ID = "Steam"; private const string PLATFORM_NAME = _("Steam"); + private const string PLATFORM_UID_PREFIX = "steam"; private static Platform platform; static construct { - platform = new GenericPlatform (PLATFORM_ID, PLATFORM_NAME); + platform = new GenericPlatform (PLATFORM_ID, PLATFORM_NAME, PLATFORM_UID_PREFIX); // Add directories where Steam installs icons var home = Environment.get_home_dir (); diff --git a/plugins/turbografx-cd/src/turbografx-cd-plugin.vala b/plugins/turbografx-cd/src/turbografx-cd-plugin.vala index 1abe0cc0..bde08da1 100644 --- a/plugins/turbografx-cd/src/turbografx-cd-plugin.vala +++ b/plugins/turbografx-cd/src/turbografx-cd-plugin.vala @@ -1,19 +1,19 @@ // This file is part of GNOME Games. License: GPL-3.0+. private class Games.TurboGrafxCDPlugin : Object, Plugin { - private const string FINGERPRINT_PREFIX = "pc-engine"; private const string PHONY_MIME_TYPE = "application/x-pc-engine-cd-rom"; private const string CUE_MIME_TYPE = "application/x-cue"; private const string CD_MAGIC_VALUE = "PC Engine CD-ROM SYSTEM"; private const string PLATFORM_ID = "TurboGrafxCD"; /* translators: known as "CD-ROM²" in eastern Asia and France */ private const string PLATFORM_NAME = _("TurboGrafx-CD"); + private const string PLATFORM_UID_PREFIX = "pc-engine"; private static RetroPlatform platform; static construct { string[] mime_types = { CUE_MIME_TYPE, PHONY_MIME_TYPE }; - platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, mime_types); + platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, mime_types, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { @@ -36,7 +36,7 @@ private class Games.TurboGrafxCDPlugin : Object, Plugin { if (!is_valid_disc (uri)) throw new TurboGrafxCDError.INVALID_DISC ("“%s” isn’t a valid TurboGrafx-CD disc.", uri.to_string ()); - var uid = new FingerprintUid (uri, FINGERPRINT_PREFIX); + var uid = new FingerprintUid (uri, PLATFORM_UID_PREFIX); var title = new FilenameTitle (uri); var media = new GriloMedia (title, PHONY_MIME_TYPE); var cover = new CompositeCover ({ diff --git a/plugins/virtual-boy/src/virtual-boy-plugin.vala b/plugins/virtual-boy/src/virtual-boy-plugin.vala index c6ce11c2..88463241 100644 --- a/plugins/virtual-boy/src/virtual-boy-plugin.vala +++ b/plugins/virtual-boy/src/virtual-boy-plugin.vala @@ -1,15 +1,15 @@ // This file is part of GNOME Games. License: GPL-3.0+. private class Games.VirtualBoyPlugin : Object, Plugin { - private const string FINGERPRINT_PREFIX = "virtual-boy"; private const string MIME_TYPE = "application/x-virtual-boy-rom"; private const string PLATFORM_ID = "VirtualBoy"; private const string PLATFORM_NAME = _("Virtual Boy"); + private const string PLATFORM_UID_PREFIX = "virtual-boy"; private static RetroPlatform platform; static construct { - platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }); + platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { @@ -34,7 +34,7 @@ private class Games.VirtualBoyPlugin : Object, Plugin { var header = new VirtualBoyHeader (file); header.check_validity (); - var uid = new FingerprintUid (uri, FINGERPRINT_PREFIX); + var uid = new FingerprintUid (uri, PLATFORM_UID_PREFIX); var title = new FilenameTitle (uri); var media = new GriloMedia (title, MIME_TYPE); var cover = new CompositeCover ({ diff --git a/plugins/wii/src/wii-plugin.vala b/plugins/wii/src/wii-plugin.vala index 3b3b9e6a..b9e3aadd 100644 --- a/plugins/wii/src/wii-plugin.vala +++ b/plugins/wii/src/wii-plugin.vala @@ -4,11 +4,12 @@ private class Games.WiiPlugin : Object, Plugin { private const string MIME_TYPE = "application/x-wii-rom"; private const string PLATFORM_ID = "Wii"; private const string PLATFORM_NAME = _("Wii"); + private const string PLATFORM_UID_PREFIX = "wii"; private static RetroPlatform platform; static construct { - platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }); + platform = new RetroPlatform (PLATFORM_ID, PLATFORM_NAME, { MIME_TYPE }, PLATFORM_UID_PREFIX); } public Platform[] get_platforms () { diff --git a/src/core/platform.vala b/src/core/platform.vala index a5a57f77..6f07cec6 100644 --- a/src/core/platform.vala +++ b/src/core/platform.vala @@ -5,6 +5,8 @@ public interface Games.Platform : Object { public abstract string get_name (); + public abstract string get_uid_prefix (); + public abstract PreferencesPagePlatformsRow get_row (); public static uint hash (Platform platform) { diff --git a/src/dummy/dummy-platform.vala b/src/dummy/dummy-platform.vala index 6d83eee3..0b410867 100644 --- a/src/dummy/dummy-platform.vala +++ b/src/dummy/dummy-platform.vala @@ -9,6 +9,10 @@ public class Games.DummyPlatform : Object, Platform { return _("Unknown"); } + public string get_uid_prefix () { + return "unknown"; + } + public PreferencesPagePlatformsRow get_row () { return new PreferencesPagePlatformsGenericRow (_("Unknown")); } diff --git a/src/generic/generic-platform.vala b/src/generic/generic-platform.vala index 09abf1c4..96b0412b 100644 --- a/src/generic/generic-platform.vala +++ b/src/generic/generic-platform.vala @@ -3,10 +3,12 @@ public class Games.GenericPlatform : Object, Platform { private string name; private string id; + private string uid_prefix; - public GenericPlatform (string id, string name) { + public GenericPlatform (string id, string name, string uid_prefix) { this.id = id; this.name = name; + this.uid_prefix = uid_prefix; } public string get_id () { @@ -17,6 +19,10 @@ public class Games.GenericPlatform : Object, Platform { return name; } + public string get_uid_prefix () { + return uid_prefix; + } + public PreferencesPagePlatformsRow get_row () { return new PreferencesPagePlatformsGenericRow (name); } diff --git a/src/retro/retro-platform.vala b/src/retro/retro-platform.vala index b5faf0f9..a84838e4 100644 --- a/src/retro/retro-platform.vala +++ b/src/retro/retro-platform.vala @@ -4,11 +4,13 @@ public class Games.RetroPlatform : Object, Platform { private string name; private string id; private string[] mime_types; + private string prefix; - public RetroPlatform (string id, string name, string[] mime_types) { + public RetroPlatform (string id, string name, string[] mime_types, string prefix) { this.id = id; this.name = name; this.mime_types = mime_types; + this.prefix = prefix; } public string get_id () { @@ -19,6 +21,10 @@ public class Games.RetroPlatform : Object, Platform { return name; } + public string get_uid_prefix () { + return prefix; + } + public string[] get_mime_types () { return mime_types; } diff --git a/src/ui/application.vala b/src/ui/application.vala index 747eb119..4423ee79 100644 --- a/src/ui/application.vala +++ b/src/ui/application.vala @@ -290,7 +290,7 @@ public class Games.Application : Gtk.Application { } var platform_name = simple_type.get_platform_name (); - var platform = new RetroPlatform (simple_type.platform, platform_name, { simple_type.mime_type }); + var platform = new RetroPlatform (simple_type.platform, platform_name, { simple_type.mime_type }, simple_type.prefix); platform_register.add_platform (platform); var game_uri_adapter = new RetroSimpleGameUriAdapter (simple_type, platform); -- GitLab From c5d9ab1f08b04cbcd629d7956dd0250adca903da Mon Sep 17 00:00:00 2001 From: Yetizone Date: Tue, 18 Jun 2019 22:35:20 +0300 Subject: [PATCH 02/24] file-operations: Changes used for implementing the file migrator Remove unnecessary parameter Rename two parameters Add copy_dir(), copy_contents() and copy_recursively() --- src/ui/application.vala | 2 +- src/utils/file-operations.vala | 45 +++++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/ui/application.vala b/src/ui/application.vala index 4423ee79..7a26573c 100644 --- a/src/ui/application.vala +++ b/src/ui/application.vala @@ -458,6 +458,6 @@ public class Games.Application : Gtk.Application { var data_dir = File.new_for_path (Application.get_data_dir ()); string[] database = { Application.get_database_path () }; - FileOperations.compress_dir (file_path, data_dir, data_dir, database); + FileOperations.compress_dir (file_path, data_dir, database); } } diff --git a/src/utils/file-operations.vala b/src/utils/file-operations.vala index 6ee83580..41d92c84 100644 --- a/src/utils/file-operations.vala +++ b/src/utils/file-operations.vala @@ -16,13 +16,13 @@ public errordomain Games.ExtractionError { } public class Games.FileOperations { - public static void compress_dir (string name, File parent_dir, File export_data, string[]? exclude_files = null) throws CompressionError { + public static void compress_dir (string archive_path, File exported_data, string[]? exclude_files = null) throws CompressionError { var archive = new Archive.Write (); archive.add_filter_gzip (); archive.set_format_pax_restricted (); - archive.open_filename (name); + archive.open_filename (archive_path); - backup_data (parent_dir, export_data, archive, exclude_files); + backup_data (exported_data, exported_data, archive, exclude_files); if (archive.close () != Archive.Result.OK) { var error_message = _("Error: %s (%d)").printf (archive.error_string (), archive.errno ()); throw new CompressionError.CLOSING_FAILED (error_message); @@ -179,4 +179,43 @@ public class Games.FileOperations { if (last_result != Archive.Result.EOF) throw new ExtractionError.DIDNT_REACH_EOF ("%s (%d)", restore_archive.error_string (), restore_archive.errno ()); } + + public static void copy_dir (File src, File dest) throws Error { + copy_recursively (src, dest, false); + } + + public static void copy_contents (File src, File dest) throws Error { + copy_recursively (src, dest, true); + } + + // If the merge_flag is set to true then the copy operation will behave + // similarly to how the file system does merging when copy & pasting + private static void copy_recursively (File src, File dest, bool merge_flag) throws Error { + var src_type = src.query_file_type (FileQueryInfoFlags.NONE); + + if (src_type == FileType.DIRECTORY) { + if (!dest.query_exists () || !merge_flag) { + dest.make_directory (); + src.copy_attributes (dest, FileCopyFlags.NONE); + } + + var src_path = src.get_path (); + var dest_path = dest.get_path (); + var enumerator = src.enumerate_children (FileAttribute.STANDARD_NAME, FileQueryInfoFlags.NONE); + + for (var info = enumerator.next_file (); info != null; info = enumerator.next_file ()) { + // src_object is any file found in the src directory (could be + // a file or another directory) + var info_name = info.get_name (); + var src_object_path = Path.build_filename (src_path, info_name); + var src_object = File.new_for_path (src_object_path); + var dest_object_path = Path.build_filename (dest_path, info_name); + var dest_object = File.new_for_path (dest_object_path); + + copy_recursively (src_object, dest_object, merge_flag); + } + } + else if (src_type == FileType.REGULAR) + src.copy (dest, FileCopyFlags.NONE); + } } -- GitLab From 25f9d539ffe273c32b3dae7cde41f16e6679399f Mon Sep 17 00:00:00 2001 From: Yetizone Date: Mon, 22 Jul 2019 14:30:08 +0300 Subject: [PATCH 03/24] migrator: Implement automatic migration --- src/core/migrator.vala | 270 ++++++++++++++++++++++++++++++++++++++++ src/meson.build | 1 + src/ui/application.vala | 5 + 3 files changed, 276 insertions(+) create mode 100644 src/core/migrator.vala diff --git a/src/core/migrator.vala b/src/core/migrator.vala new file mode 100644 index 00000000..e3f4353b --- /dev/null +++ b/src/core/migrator.vala @@ -0,0 +1,270 @@ +// This file is part of GNOME Games. License: GPL-3.0+. + +public class Games.Migrator : Object { + // Returns true if the migration wasn't necessary or + // if it was performed succesfully + public static bool apply_migration_if_necessary () { + var data_dir_path = Application.get_data_dir (); + var data_dir = File.new_for_path (data_dir_path); + + var backup_archive_path = Path.build_filename (data_dir_path, "exported_data.zip"); + var backup_archive = File.new_for_path (backup_archive_path); + var database_path = Application.get_database_path (); + string[] backup_excluded_files = { database_path, backup_archive_path }; + + var version_file = data_dir.get_child (".version"); + + // If the version file exists, there's no need + // to apply the migration + if (version_file.query_exists ()) + return true; + + info ("[Migrator]: Migration is necessary"); + + // Attempt to create a backup of the previous data + try { + backup_archive.create (FileCreateFlags.NONE); + FileOperations.compress_dir (backup_archive_path, data_dir, backup_excluded_files); + } + catch (Error e) { + critical ("Unable to backup data, aborting migration: %s", e.message); + return false; + } + + try { + // The migration executes file I/O which may result in errors being + // thrown + apply_migration (version_file); + } + catch (Error e) { + critical ("Migration failed: %s", e.message); + + // Delete all directories from the data dir + var savestates_dir_path = Path.build_filename (data_dir_path, "/savestates"); + var savestates_dir = File.new_for_path (savestates_dir_path); + + delete_files_no_errors (savestates_dir); + delete_old_directories (); + + // Attempt to restore data from backup + if (try_restore_data (backup_archive_path, data_dir_path, backup_excluded_files)) { + // Successfully restored data from backup, deleting backup + delete_files_no_errors (backup_archive); + } + else { + // Something went seriously wrong here + // Migration failed and restoring backup data also failed + assert_not_reached (); + } + + return false; + } + + // Migration applied succesfully, deleting backup + delete_files_no_errors (backup_archive); + + return true; + } + + private static void apply_migration (File version_file) throws Error { + // Create the version file + version_file.create (FileCreateFlags.NONE); + + // Create the savestates dir + var savestates_dir = File.new_for_path (get_savestates_dir_path ()); + savestates_dir.make_directory (); + + // Currently any game has only one snapshot file + // So for every snapshot file create a savestate + var snapshots_dir_path = get_old_snapshots_dir_path (); + var snapshots_dir = Dir.open (snapshots_dir_path, 0); + var file_name = ""; + while ((file_name = snapshots_dir.read_name ()) != null) { + var file_name_tokens = file_name.split (".snapshot"); + + if (file_name_tokens.length != 2) + continue; // Not a snapshot file + + // The snapshot files are curently named "[game_uid].snapshot" + var game_uid = file_name_tokens[0]; + create_first_game_savestate (game_uid); + } + + delete_old_directories (); + } + + private static void create_first_game_savestate (string game_uid) throws Error { + // Inside the savestates dir there will be a sub-dir for each game + // which will contain all of the savestates for that game + // These sub-dirs will be named "[game_uid]-[core]" + + // Getting the core_id + var platform = platform_from_game_uid (game_uid); + var core_manager = RetroCoreManager.get_instance (); + var preferred_core = core_manager.get_preferred_core (platform); + var core_id = preferred_core.get_id (); + + // Create the directory for the game's savestates + var core_id_prefix = core_id.replace (".libretro", ""); // Remove the ".libretro" from the core_id + var game_savestates_dir_name = game_uid + "-" + core_id_prefix; + var game_savestates_dir_path = Path.build_filename (get_savestates_dir_path (), game_savestates_dir_name); + var game_savestates_dir = File.new_for_path (game_savestates_dir_path); + + game_savestates_dir.make_directory (); + + // Create the directory for the first savestate + var now_time = new DateTime.now (); + var now_time_str = now_time.to_string (); + var savestate_dir_path = Path.build_filename (game_savestates_dir_path, now_time_str); + var savestate_dir = File.new_for_path (savestate_dir_path); + + savestate_dir.make_directory (); + + // Use the currently existing game data (snapshot, screenshot, + // save file, save dir) to populate the savestate + var snapshots_dir_path = get_old_snapshots_dir_path (); + var snapshot_path = Path.build_filename (snapshots_dir_path, game_uid + ".snapshot"); + var screenshot_path = Path.build_filename (snapshots_dir_path, game_uid + ".png"); + var saves_dir = get_old_saves_dir_path (); + var save_dir_path = Path.build_filename (saves_dir, game_uid); + var save_file_path = save_dir_path + ".save"; + var medias_dir = get_old_medias_dir_path (); + var media_file_path = Path.build_filename (medias_dir, game_uid + ".media"); + + var snapshot_file = File.new_for_path (snapshot_path); + var screenshot_file = File.new_for_path (screenshot_path); + var save_dir = File.new_for_path (save_dir_path); + var save_file = File.new_for_path (save_file_path); + var media_file = File.new_for_path (media_file_path); + + var savestate_snapshot_file_path = Path.build_filename (savestate_dir_path, "snapshot"); + var savestate_snapshot_file = File.new_for_path (savestate_snapshot_file_path); + FileOperations.copy_contents (snapshot_file, savestate_snapshot_file); + + var savestate_screenshot_file_path = Path.build_filename (savestate_dir_path, "screenshot"); + var savestate_screenshot_file = File.new_for_path (savestate_screenshot_file_path); + FileOperations.copy_contents (screenshot_file, savestate_screenshot_file); + + if (!save_dir.query_exists ()) + save_dir.make_directory (); + + var savestate_save_dir_path = Path.build_filename (savestate_dir_path, "save-dir"); + var savestate_save_dir = File.new_for_path (savestate_save_dir_path); + FileOperations.copy_dir (save_dir, savestate_save_dir); + + if (save_file.query_exists ()) { + var savestate_save_file_path = Path.build_filename (savestate_dir_path, "save"); + var savestate_save_file = File.new_for_path (savestate_save_file_path); + FileOperations.copy_contents (save_file, savestate_save_file); + } + + if (media_file.query_exists ()) { + var savestate_media_file_path = Path.build_filename (savestate_dir_path, "media"); + var savestate_media_file = File.new_for_path (savestate_media_file_path); + FileOperations.copy_contents (media_file, savestate_media_file); + } + + // Create a KeyFile with additional data + var metadata = new KeyFile (); + var metadata_file_path = Path.build_filename (savestate_dir_path, "metadata"); + + // Automatic means whether the savestate was created automatically when + // quitting/loading the game or manually by the user using the Save button + metadata.set_boolean ("Metadata", "Automatic", true); + metadata.set_string ("Metadata", "Creation Date", now_time_str); + metadata.set_string ("Metadata", "Platform", platform.get_uid_prefix ()); + metadata.set_string ("Metadata", "Core", core_id); + metadata.save_to_file (metadata_file_path); + } + + + private static RetroPlatform platform_from_game_uid (string game_uid) { + // [game_uid] is currently formed as "[platform]-[hash]" + // So we can use the game_uid to get the platform + var platforms_register = PlatformRegister.get_register (); + var platforms = platforms_register.get_all_platforms (); + + string best_match = null; + RetroPlatform result_platform = null; + + foreach (var platform in platforms) { + var retro_platform = platform as RetroPlatform; + + if (retro_platform == null) + continue; // not a RetroPlatform + + var platform_uid_prefix = platform.get_uid_prefix (); + + if (game_uid.contains (platform_uid_prefix)) { + if (best_match == null || platform_uid_prefix.length > best_match.length) { + best_match = platform_uid_prefix; + result_platform = retro_platform; + } + } + } + + return result_platform; + } + + // Delete the old snapshots, saves and medias directories + private static void delete_old_directories () { + var snapshots_dir_path = get_old_snapshots_dir_path (); + var saves_dir_path = get_old_saves_dir_path (); + var medias_dir_path = get_old_medias_dir_path (); + + var snapshots_dir = File.new_for_path (snapshots_dir_path); + var saves_dir = File.new_for_path (saves_dir_path); + var medias_dir = File.new_for_path (medias_dir_path); + + delete_files_no_errors (snapshots_dir); + delete_files_no_errors (saves_dir); + delete_files_no_errors (medias_dir); + } + + private static string get_old_snapshots_dir_path () { + var data_dir = Application.get_data_dir (); + + return @"$data_dir/snapshots"; + } + + private static string get_old_saves_dir_path () { + var data_dir = Application.get_data_dir (); + + return @"$data_dir/saves"; + } + + private static string get_old_medias_dir_path () { + var data_dir = Application.get_data_dir (); + + return @"$data_dir/medias"; + } + + private static string get_savestates_dir_path () { + var data_dir = Application.get_data_dir (); + + return @"$data_dir/savestates"; + } + + // Method for deleting files that treats errors locally because there isn't + // much that can go wrong with deleting files + private static void delete_files_no_errors (File file) { + try { + FileOperations.delete_files (file, {}); + } + catch (Error e) { + warning ("Cannot delete file %s: %s", file.get_path (), e.message); + } + } + + private static bool try_restore_data (string backup_archive_path, string data_dir_path, string[] backup_excluded_files) { + try { + FileOperations.extract_archive (backup_archive_path, data_dir_path, backup_excluded_files); + } + catch (Error e) { + critical ("Failed to restore data from backup archive %s: %s", backup_archive_path, e.message); + return false; + } + + return true; // Succesfully restored data from backup + } +} diff --git a/src/meson.build b/src/meson.build index 86c0a3a1..bee993dc 100644 --- a/src/meson.build +++ b/src/meson.build @@ -30,6 +30,7 @@ vala_sources = [ 'core/media-info.vala', 'core/media-set/media-set.vala', 'core/media-set/media-set-error.vala', + 'core/migrator.vala', 'core/platform.vala', 'core/platform-register.vala', 'core/players.vala', diff --git a/src/ui/application.vala b/src/ui/application.vala index 7a26573c..4ac96e88 100644 --- a/src/ui/application.vala +++ b/src/ui/application.vala @@ -329,6 +329,11 @@ public class Games.Application : Gtk.Application { debug ("Error: %s", e.message); } } + + // Re-organize data_dir layout if necessary + // This operation has to be executed _after_ the PlatformsRegister has + // been populated and therefore this call is placed here + Migrator.apply_migration_if_necessary (); } private async Game? game_for_uris (Uri[] uris) { -- GitLab From 19bffd9a30e44d5f7a5be33580063b15116f3c79 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Sat, 11 May 2019 20:46:30 +0300 Subject: [PATCH 04/24] retro-runner: Use savestates according to new directory layout --- src/retro/retro-core-source.vala | 4 + src/retro/retro-runner.vala | 145 +++++++++++++++++++------------ src/ui/application.vala | 3 + src/ui/display-view.vala | 13 +++ 4 files changed, 110 insertions(+), 55 deletions(-) diff --git a/src/retro/retro-core-source.vala b/src/retro/retro-core-source.vala index 7b75ecaa..5343bd92 100644 --- a/src/retro/retro-core-source.vala +++ b/src/retro/retro-core-source.vala @@ -13,6 +13,10 @@ public class Games.RetroCoreSource : Object { return platform; } + public string get_core_id () throws Error { + return core_descriptor.get_id (); + } + public string get_module_path () throws Error { ensure_module_is_found (); diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 8da75815..cb3250dc 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -15,14 +15,15 @@ public class Games.RetroRunner : Object, Runner { public bool can_resume { get { try { + // Check if there are any existing savestates init (); if (!core.get_can_access_state ()) return false; - var snapshot_path = get_snapshot_path (); - var file = File.new_for_path (snapshot_path); + var game_savestates_dir_path = get_game_savestates_dir_path (); + var game_savestates_dir = Dir.open (game_savestates_dir_path); - return file.query_exists (); + return game_savestates_dir.read_name () != null; } catch (Error e) { warning (e.message); @@ -52,7 +53,6 @@ public class Games.RetroRunner : Object, Runner { private string save_directory_path; private string save_path; - private string snapshot_path; private string screenshot_path; private Retro.CoreDescriptor core_descriptor; @@ -164,16 +164,33 @@ public class Games.RetroRunner : Object, Runner { loop.stop (); if (!is_ready) { - load_ram (); - core.reset (); - load_snapshot (); - is_ready = true; + load_latest_savestate (); } loop.start (); running = true; } + private void load_latest_savestate () throws Error { + var game_savestates_dir_path = get_game_savestates_dir_path (); + var game_savestates_dir = Dir.open (game_savestates_dir_path); + + string latest_savestate_name = null; + string dir_entry = null; + + while ((dir_entry = game_savestates_dir.read_name ()) != null) { + latest_savestate_name = dir_entry; + } + + var latest_savestate_dir_path = Path.build_filename (game_savestates_dir_path, latest_savestate_name); + var latest_savestate_dir = File.new_for_path (latest_savestate_dir_path); + + //load_ram (); + core.reset (); + load_snapshot (latest_savestate_dir); + is_ready = true; + } + private void init () throws Error { if (is_initialized) return; @@ -280,8 +297,19 @@ public class Games.RetroRunner : Object, Runner { return; loop.stop (); + + //FIXME: + // In the future here there will be code which updates the currently + // used temporary savestate + running = false; + } + public void stop () { + if (!is_initialized) + return; + + pause (); try { save (); @@ -289,13 +317,7 @@ public class Games.RetroRunner : Object, Runner { catch (Error e) { warning (e.message); } - } - public void stop () { - if (!is_initialized) - return; - - pause (); deinit (); stopped (); @@ -356,29 +378,53 @@ public class Games.RetroRunner : Object, Runner { return; } + } - try { - save_media_data (); + private string get_game_savestates_dir_path () throws Error { + // Get the savestates directory of the game currently being run + + var data_dir_path = Application.get_data_dir (); + var savestates_dir_path = Path.build_filename (data_dir_path, "savestates"); + var uid = uid.get_uid (); + + string core_id = null; + + if (core_descriptor != null) { + core_id = core_descriptor.get_id (); } - catch (Error e) { - warning (e.message); + else { + core_id = core_source.get_core_id (); } + + var core_id_prefix = core_id.replace (".libretro", ""); + + return Path.build_filename (savestates_dir_path, uid + "-" + core_id_prefix); } - private void save () throws Error { + // FIXME: This should be private, but it is public because of a temporary + // hack used in the DisplayView + public void save () throws Error { if (!should_save) return; - save_ram (); + // Create a new savestate + var game_savestates_dir_path = get_game_savestates_dir_path (); + var now_time_str = TimeVal ().to_iso8601 (); + var new_savestate_path = Path.build_filename (game_savestates_dir_path, now_time_str); + var new_savestate_dir = File.new_for_path (new_savestate_path); + + new_savestate_dir.make_directory (); + + save_ram (new_savestate_dir); if (media_set.get_size () > 1) - save_media_data (); + save_media_data (new_savestate_dir); if (!core.get_can_access_state ()) return; - save_snapshot (); - save_screenshot (); + save_snapshot (new_savestate_dir); + save_screenshot (new_savestate_dir); should_save = false; } @@ -406,6 +452,7 @@ public class Games.RetroRunner : Object, Runner { return save_directory_path; } + // TODO: To be removed private string get_save_path () throws Error { if (save_path != null) return save_path; @@ -417,16 +464,14 @@ public class Games.RetroRunner : Object, Runner { return save_path; } - private void save_ram () throws Error{ + private void save_ram (File savestate_dir) throws Error{ var bytes = core.get_memory (Retro.MemoryType.SAVE_RAM); var save = bytes.get_data (); if (save.length == 0) return; - var dir = Application.get_saves_dir (); - Application.try_make_dir (dir); - - var save_path = get_save_path (); + var savestate_dir_path = savestate_dir.get_path (); + var save_path = Path.build_filename (savestate_dir_path, "save"); FileUtils.set_data (save_path, save); } @@ -448,34 +493,22 @@ public class Games.RetroRunner : Object, Runner { core.set_memory (Retro.MemoryType.SAVE_RAM, bytes); } - private string get_snapshot_path () throws Error { - if (snapshot_path != null) - return snapshot_path; - - var dir = Application.get_snapshots_dir (); - var uid = uid.get_uid (); - snapshot_path = @"$dir/$uid.snapshot"; - - return snapshot_path; - } - - private void save_snapshot () throws Error { + private void save_snapshot (File savestate_dir) throws Error { var bytes = core.get_state (); var buffer = bytes.get_data (); - var dir = Application.get_snapshots_dir (); - Application.try_make_dir (dir); - - var snapshot_path = get_snapshot_path (); + var savestate_dir_path = savestate_dir.get_path (); + var snapshot_path = Path.build_filename (savestate_dir_path, "snapshot"); FileUtils.set_data (snapshot_path, buffer); } - private void load_snapshot () throws Error { + private void load_snapshot (File savestate_dir) throws Error { if (!core.get_can_access_state ()) return; - var snapshot_path = get_snapshot_path (); + var savestate_dir_path = savestate_dir.get_path (); + var snapshot_path = Path.build_filename (savestate_dir_path, "snapshot"); if (!FileUtils.test (snapshot_path, FileTest.EXISTS)) return; @@ -487,15 +520,13 @@ public class Games.RetroRunner : Object, Runner { core.set_state (bytes); } - private void save_media_data () throws Error { - var dir = Application.get_medias_dir (); - Application.try_make_dir (dir); - - var medias_path = get_medias_path (); + private void save_media_data (File savestate_dir) throws Error { + var savestate_dir_path = savestate_dir.get_path (); + var media_path = Path.build_filename (savestate_dir_path, "media"); string contents = media_set.selected_media_number.to_string (); - FileUtils.set_contents (medias_path, contents, contents.length); + FileUtils.set_contents (media_path, contents, contents.length); } private void load_media_data () throws Error { @@ -511,6 +542,7 @@ public class Games.RetroRunner : Object, Runner { media_set.selected_media_number = disc_num; } + // TODO: To be removed private string get_medias_path () throws Error { var dir = Application.get_medias_dir (); var uid = uid.get_uid (); @@ -518,18 +550,20 @@ public class Games.RetroRunner : Object, Runner { return @"$dir/$uid.media"; } + // TODO: To be removed private string get_screenshot_path () throws Error { if (screenshot_path != null) return screenshot_path; var dir = Application.get_snapshots_dir (); var uid = uid.get_uid (); - screenshot_path = @"$dir/$uid.png"; + var now_time_str = TimeVal ().to_iso8601 (); + screenshot_path = @"$dir/$uid/$now_time_str.png"; return screenshot_path; } - private void save_screenshot () throws Error { + private void save_screenshot (File savestate_dir) throws Error { if (!core.get_can_access_state ()) return; @@ -537,7 +571,8 @@ public class Games.RetroRunner : Object, Runner { if (pixbuf == null) return; - var screenshot_path = get_screenshot_path (); + var savestate_dir_path = savestate_dir.get_path (); + var screenshot_path = Path.build_filename (savestate_dir_path, "screenshot"); var now = new GLib.DateTime.now_local (); var creation_time = now.to_string (); diff --git a/src/ui/application.vala b/src/ui/application.vala index 4ac96e88..5e08c616 100644 --- a/src/ui/application.vala +++ b/src/ui/application.vala @@ -86,12 +86,14 @@ public class Games.Application : Gtk.Application { return @"$data_dir/gnome-games"; } + // TODO: To be removed public static string get_saves_dir () { var data_dir = get_data_dir (); return @"$data_dir/saves"; } + // TODO: To be removed public static string get_snapshots_dir () { var data_dir = get_data_dir (); @@ -145,6 +147,7 @@ public class Games.Application : Gtk.Application { } } + // TODO: To be removed public static string get_medias_dir () { var data_dir = get_data_dir (); diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala index d4859feb..325f2ada 100644 --- a/src/ui/display-view.vala +++ b/src/ui/display-view.vala @@ -344,6 +344,19 @@ private class Games.DisplayView : Object, UiView { box.runner.pause (); + // FIXME: Temporary hack used to avoid displaying the Quit Dialog when + // not necessary + + var retro_runner = box.runner as RetroRunner; + if (retro_runner != null) { + try { + retro_runner.save (); + } + catch (Error e) { + critical (e.message); + } + } + if (box.runner.can_quit_safely) { box.runner.stop (); -- GitLab From 457bdc2111287023c2314f8c27693f6c22f6dcdd Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 30 May 2019 15:32:23 +0300 Subject: [PATCH 05/24] retro-runner: Add abstractions for savestates --- src/command/command-runner.vala | 3 + src/core/runner.vala | 3 +- src/core/savestate.vala | 253 ++++++++++++++++++++++++++++++++ src/dummy/dummy-runner.vala | 3 + src/meson.build | 1 + src/retro/retro-runner.vala | 173 +++++++--------------- src/ui/display-view.vala | 16 +- 7 files changed, 321 insertions(+), 131 deletions(-) create mode 100644 src/core/savestate.vala diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index 8db2a5e0..03256016 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -82,6 +82,9 @@ public class Games.CommandRunner : Object, Runner { public void stop () { } + public void attempt_create_savestate () { + } + public InputMode[] get_available_input_modes () { return { }; } diff --git a/src/core/runner.vala b/src/core/runner.vala index e5374866..df28e351 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -16,8 +16,9 @@ public interface Games.Runner : Object { public abstract void resume () throws Error; public abstract void pause (); public abstract void stop (); - public abstract InputMode[] get_available_input_modes (); + public abstract void attempt_create_savestate () throws Error; + public abstract InputMode[] get_available_input_modes (); public abstract bool key_press_event (Gdk.EventKey event); public abstract bool gamepad_button_press_event (uint16 button); } diff --git a/src/core/savestate.vala b/src/core/savestate.vala new file mode 100644 index 00000000..77deb3f1 --- /dev/null +++ b/src/core/savestate.vala @@ -0,0 +1,253 @@ +public class Games.Savestate : Object { + private string path; // Path to the savestate directory + + public Savestate (string path) { + this.path = path; + } + + public string? get_name () { + var metadata = new KeyFile (); + var metadata_file_path = Path.build_filename (path, "metadata"); + + try { + metadata.load_from_file (metadata_file_path, KeyFileFlags.NONE); + var is_automatic = metadata.get_boolean ("Metadata", "Automatic"); + + if (is_automatic) + return null; + else + return metadata.get_string ("Metadata", "Name"); + } + catch (Error e) { + critical ("Failed to get name from metadata file for savestate at %s: %s", path, e.message); + return null; + } + } + + public DateTime? get_creation_date () { + var metadata = new KeyFile (); + var metadata_file_path = Path.build_filename (path, "metadata"); + + try { + metadata.load_from_file (metadata_file_path, KeyFileFlags.NONE); + var creation_date_str = metadata.get_string ("Metadata", "Creation Date"); + + return new DateTime.from_iso8601 (creation_date_str, new TimeZone.local ()); + } + catch (Error e) { + critical ("Failed to get creation date from metadata file for savestate at %s: %s", path, e.message); + return null; + } + } + + public void set_snapshot_data (Bytes snapshot_data) throws Error { + var buffer = snapshot_data.get_data (); + var snapshot_path = Path.build_filename (path, "snapshot"); + + FileUtils.set_data (snapshot_path, buffer); + } + + public Bytes get_snapshot_data () throws Error { + var snapshot_path = Path.build_filename (path, "snapshot"); + + uint8[] data = null; + FileUtils.get_data (snapshot_path, out data); + var bytes = new Bytes.take (data); + + return bytes; + } + + public string get_save_ram_path () { + return Path.build_filename (path, "save"); + } + + public void set_save_ram_data (uint8[] save_ram_data) throws Error { + var save_ram_path = Path.build_filename (path, "save"); + + FileUtils.set_data (save_ram_path, save_ram_data); + } + + public string get_screenshot_path () { + return Path.build_filename (path, "screenshot"); + } + + public string get_save_directory_path () { + return Path.build_filename (path, "save-dir"); + } + + public bool has_media_data () { + var media_path = Path.build_filename (path, "media"); + + return FileUtils.test (media_path, FileTest.EXISTS); + } + + // Currently all games only have a number as media_data, so this method + // returns an int, but in the future it might return an abstract MediaData + public int get_media_data () throws Error { + var media_path = Path.build_filename (path, "media"); + + if (!FileUtils.test (media_path, FileTest.EXISTS)) + throw new FileError.ACCES ("Savestate at %s does not contain media file", path); + + string contents; + FileUtils.get_contents (media_path, out contents); + + int media_number = int.parse (contents); + + return media_number; + } + + public void set_media_data (MediaSet media_set) throws Error { + var media_path = Path.build_filename (path, "media"); + var contents = media_set.selected_media_number.to_string (); + + FileUtils.set_contents (media_path, contents, contents.length); + } + + public Savestate clone_in_tmp () throws Error { + var tmp_savestate_path = prepare_empty_savestate_in_tmp (); + var tmp_savestate_dir = File.new_for_path (tmp_savestate_path); + var cloned_savestate_dir = File.new_for_path (path); + + FileOperations.copy_contents (cloned_savestate_dir, tmp_savestate_dir); + + return new Savestate (tmp_savestate_path); + } + + // This method is used to save the savestate in /tmp as a regular savestate + // inside the savestates directory of a game + // It names the newly created savestate using the creation date in the + // metadata file + public void save_in (string game_savestates_dir_path) throws Error { + var metadata = new KeyFile (); + var metadata_file_path = Path.build_filename (path, "metadata"); + metadata.load_from_file (metadata_file_path, KeyFileFlags.NONE); + + var creation_date = metadata.get_string ("Metadata", "Creation Date"); + var copied_dir = File.new_for_path (path); + var new_savestate_dir_path = Path.build_filename (game_savestates_dir_path, creation_date); + var new_savestate_dir = File.new_for_path (new_savestate_dir_path); + + FileOperations.copy_dir (copied_dir, new_savestate_dir); + } + + // Set the metadata for an automatic savestate + public void set_metadata_automatic (DateTime creation_date, string platform, string core) throws Error { + set_metadata (true, null, creation_date, platform, core); + } + + // Set the metadata for a manual savestate + public void set_metadata_manual (string name, DateTime creation_date, string platform, string core) throws Error { + set_metadata (false, name, creation_date, platform, core); + } + + private void set_metadata (bool is_automatic, string? name, DateTime creation_date, + string platform, string core) throws Error { + var metadata_file_path = Path.build_filename (path, "metadata"); + var metadata_file = File.new_for_path (metadata_file_path); + var metadata = new KeyFile (); + + if (metadata_file.query_exists ()) + metadata_file.@delete (); + + metadata.set_boolean ("Metadata", "Automatic", is_automatic); + + if (name != null) + metadata.set_string ("Metadata", "Name", name); + + metadata.set_string ("Metadata", "Creation Date", creation_date.to_string ()); + metadata.set_string ("Metadata", "Platform", platform); + metadata.set_string ("Metadata", "Core", core); + metadata.save_to_file (metadata_file_path); + } + + // Automatic means whether the savestate was created automatically when + // quitting/loading the game or manually by the user using the Save button + public bool is_automatic () { + var metadata = new KeyFile (); + var metadata_file_path = Path.build_filename (path, "metadata"); + + try { + metadata.load_from_file (metadata_file_path, KeyFileFlags.NONE); + return metadata.get_boolean ("Metadata", "Automatic"); + } + catch (Error e) { + critical ("Failed to get Automatic field from metadata file for savestate at %s: %s", path, e.message); + return false; + } + } + + public void delete_from_disk () { + var savestate_dir = File.new_for_path (path); + + // Treat errors locally in this method because there isn't much that + // can go wrong with deleting files + try { + FileOperations.delete_files (savestate_dir, {}); + } + catch (Error e) { + warning ("Failed to delete savestate at %s: %s", path, e.message); + } + } + + public static Savestate[] get_game_savestates (Uid game_uid, string core_id) throws Error { + var data_dir_path = Application.get_data_dir (); + var savestates_dir_path = Path.build_filename (data_dir_path, "savestates"); + var uid_str = game_uid.get_uid (); + var core_id_prefix = core_id.replace (".libretro", ""); + var game_savestates_dir_path = Path.build_filename (savestates_dir_path, uid_str + "-" + core_id_prefix); + var game_savestates_dir_file = File.new_for_path (game_savestates_dir_path); + + if (!game_savestates_dir_file.query_exists ()) { + // The game has no savestates directory so we create one + game_savestates_dir_file.make_directory_with_parents (); + return {}; // Obviously no savestates available either + } + + var game_savestates_dir = Dir.open (game_savestates_dir_path); + + Savestate[] game_savestates = {}; + string savestate_name = null; + + while ((savestate_name = game_savestates_dir.read_name ()) != null) { + var savestate_path = Path.build_filename (game_savestates_dir_path, savestate_name); + game_savestates += new Savestate (savestate_path); + } + + // Sort the savestates array by creation dates + qsort_with_data (game_savestates, sizeof (Savestate), compare_savestates_creation_date); + + return game_savestates; + } + + private static int compare_savestates_creation_date (Savestate s1, Savestate s2) { + // We want the savestates with the latest creation dates to be the first in the array + var s1_creation_date_str = s1.get_creation_date ().to_string (); + var s2_creation_date_str = s2.get_creation_date ().to_string (); + + if (s1_creation_date_str > s2_creation_date_str) + return -1; + + if (s1_creation_date_str == s2_creation_date_str) + return 0; + + // s1_creation_date_str < s2_creation_date_str + return 1; + } + + public static Savestate create_empty_in_tmp () throws Error { + return new Savestate (prepare_empty_savestate_in_tmp ()); + } + + // Returns the path of the newly created dir in tmp + public static string prepare_empty_savestate_in_tmp () throws Error { + var tmp_savestate_path = DirUtils.make_tmp ("games_savestate_XXXXXX"); + var save_dir_path = Path.build_filename (tmp_savestate_path, "save-dir"); + var save_dir = File.new_for_path (save_dir_path); + + save_dir.make_directory (); + + return tmp_savestate_path; + } +} + diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index d82572e0..b8e697f1 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -48,6 +48,9 @@ private class Games.DummyRunner : Object, Runner { public void stop () { } + public void attempt_create_savestate () { + } + public InputMode[] get_available_input_modes () { return { }; } diff --git a/src/meson.build b/src/meson.build index bee993dc..a08f60cf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -42,6 +42,7 @@ vala_sources = [ 'core/rating.vala', 'core/release-date.vala', 'core/runner.vala', + 'core/savestate.vala', 'core/title.vala', 'core/uid.vala', 'core/uri-game-factory.vala', diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index cb3250dc..17b4900e 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -15,15 +15,15 @@ public class Games.RetroRunner : Object, Runner { public bool can_resume { get { try { - // Check if there are any existing savestates init (); + + // Check if the core can support savestates if (!core.get_can_access_state ()) return false; - var game_savestates_dir_path = get_game_savestates_dir_path (); - var game_savestates_dir = Dir.open (game_savestates_dir_path); - - return game_savestates_dir.read_name () != null; + // Check if there are any existing savestates + if (game_savestates.length != 0) + return true; } catch (Error e) { warning (e.message); @@ -51,10 +51,6 @@ public class Games.RetroRunner : Object, Runner { } } - private string save_directory_path; - private string save_path; - private string screenshot_path; - private Retro.CoreDescriptor core_descriptor; private RetroCoreSource core_source; private Platform platform; @@ -62,6 +58,8 @@ public class Games.RetroRunner : Object, Runner { private InputCapabilities input_capabilities; private Settings settings; private Title game_title; + private Savestate[] game_savestates; + private Savestate latest_savestate; private bool _running; private bool running { @@ -110,7 +108,7 @@ public class Games.RetroRunner : Object, Runner { public bool check_is_valid (out string error_message) throws Error { try { - load_media_data (); + media_set.selected_media_number = 0; init (); } catch (RetroError.MODULE_NOT_FOUND e) { @@ -140,7 +138,8 @@ public class Games.RetroRunner : Object, Runner { } public void start () throws Error { - load_media_data (); + if (latest_savestate != null && latest_savestate.has_media_data ()) + media_set.selected_media_number = latest_savestate.get_media_data (); if (!is_initialized) init (); @@ -148,7 +147,9 @@ public class Games.RetroRunner : Object, Runner { loop.stop (); if (!is_ready) { - load_ram (); + if (latest_savestate != null) + load_save_ram (latest_savestate.get_save_ram_path ()); + is_ready = true; } core.reset (); @@ -172,22 +173,15 @@ public class Games.RetroRunner : Object, Runner { } private void load_latest_savestate () throws Error { - var game_savestates_dir_path = get_game_savestates_dir_path (); - var game_savestates_dir = Dir.open (game_savestates_dir_path); - - string latest_savestate_name = null; - string dir_entry = null; - - while ((dir_entry = game_savestates_dir.read_name ()) != null) { - latest_savestate_name = dir_entry; - } + // TODO: This method assumes that there exists at least a savestate + // [Yeti]: Perhaps we should bug-proof this using an Assert ? + load_save_ram (latest_savestate.get_save_ram_path ()); + core.reset (); + core.set_state (latest_savestate.get_snapshot_data ()); - var latest_savestate_dir_path = Path.build_filename (game_savestates_dir_path, latest_savestate_name); - var latest_savestate_dir = File.new_for_path (latest_savestate_dir_path); + if (latest_savestate.has_media_data ()) + media_set.selected_media_number = latest_savestate.get_media_data (); - //load_ram (); - core.reset (); - load_snapshot (latest_savestate_dir); is_ready = true; } @@ -213,6 +207,20 @@ public class Games.RetroRunner : Object, Runner { loop = new Retro.MainLoop (core); running = false; + // Load the game's savestates if there are any + string core_id = null; + + if (core_descriptor != null) { + core_id = core_descriptor.get_id (); + } + else { + core_id = core_source.get_core_id (); + } + + game_savestates = Savestate.get_game_savestates (uid, core_id); + if (game_savestates.length != 0) + latest_savestate = game_savestates[0]; + load_screenshot (); is_initialized = true; @@ -274,9 +282,11 @@ public class Games.RetroRunner : Object, Runner { var platform_id = platform.get_id (); core.system_directory = @"$platforms_dir/$platform_id/system"; - var save_directory = get_save_directory_path (); - Application.try_make_dir (save_directory); - core.save_directory = save_directory; + if (latest_savestate != null) { + var save_directory = latest_savestate.get_save_directory_path (); + Application.try_make_dir (save_directory); + core.save_directory = save_directory; + } core.log.connect (Retro.g_log); view.set_core (core); @@ -312,7 +322,7 @@ public class Games.RetroRunner : Object, Runner { pause (); try { - save (); + attempt_create_savestate (); } catch (Error e) { warning (e.message); @@ -381,8 +391,7 @@ public class Games.RetroRunner : Object, Runner { } private string get_game_savestates_dir_path () throws Error { - // Get the savestates directory of the game currently being run - + // Get the savestates directory of the game var data_dir_path = Application.get_data_dir (); var savestates_dir_path = Path.build_filename (data_dir_path, "savestates"); var uid = uid.get_uid (); @@ -401,9 +410,7 @@ public class Games.RetroRunner : Object, Runner { return Path.build_filename (savestates_dir_path, uid + "-" + core_id_prefix); } - // FIXME: This should be private, but it is public because of a temporary - // hack used in the DisplayView - public void save () throws Error { + public void attempt_create_savestate () throws Error { if (!should_save) return; @@ -415,7 +422,7 @@ public class Games.RetroRunner : Object, Runner { new_savestate_dir.make_directory (); - save_ram (new_savestate_dir); + store_save_ram (new_savestate_dir); if (media_set.get_size () > 1) save_media_data (new_savestate_dir); @@ -441,49 +448,24 @@ public class Games.RetroRunner : Object, Runner { return @"$(Config.OPTIONS_DIR)/$options_name.options"; } - private string get_save_directory_path () throws Error { - if (save_directory_path != null) - return save_directory_path; - - var dir = Application.get_saves_dir (); - var uid = uid.get_uid (); - save_directory_path = @"$dir/$uid"; - - return save_directory_path; - } - - // TODO: To be removed - private string get_save_path () throws Error { - if (save_path != null) - return save_path; - - var dir = Application.get_saves_dir (); - var uid = uid.get_uid (); - save_path = @"$dir/$uid.save"; - - return save_path; - } - - private void save_ram (File savestate_dir) throws Error{ + private void store_save_ram (File savestate_dir) throws Error{ var bytes = core.get_memory (Retro.MemoryType.SAVE_RAM); var save = bytes.get_data (); if (save.length == 0) return; var savestate_dir_path = savestate_dir.get_path (); - var save_path = Path.build_filename (savestate_dir_path, "save"); + var save_ram_path = Path.build_filename (savestate_dir_path, "save"); - FileUtils.set_data (save_path, save); + FileUtils.set_data (save_ram_path, save); } - private void load_ram () throws Error { - var save_path = get_save_path (); - - if (!FileUtils.test (save_path, FileTest.EXISTS)) + private void load_save_ram (string save_ram_path) throws Error { + if (!FileUtils.test (save_ram_path, FileTest.EXISTS)) return; uint8[] data = null; - FileUtils.get_data (save_path, out data); + FileUtils.get_data (save_ram_path, out data); var expected_size = core.get_memory_size (Retro.MemoryType.SAVE_RAM); if (data.length != expected_size) @@ -503,23 +485,6 @@ public class Games.RetroRunner : Object, Runner { FileUtils.set_data (snapshot_path, buffer); } - private void load_snapshot (File savestate_dir) throws Error { - if (!core.get_can_access_state ()) - return; - - var savestate_dir_path = savestate_dir.get_path (); - var snapshot_path = Path.build_filename (savestate_dir_path, "snapshot"); - - if (!FileUtils.test (snapshot_path, FileTest.EXISTS)) - return; - - uint8[] data = null; - FileUtils.get_data (snapshot_path, out data); - - var bytes = new Bytes.take (data); - core.set_state (bytes); - } - private void save_media_data (File savestate_dir) throws Error { var savestate_dir_path = savestate_dir.get_path (); var media_path = Path.build_filename (savestate_dir_path, "media"); @@ -529,40 +494,6 @@ public class Games.RetroRunner : Object, Runner { FileUtils.set_contents (media_path, contents, contents.length); } - private void load_media_data () throws Error { - var medias_path = get_medias_path (); - - if (!FileUtils.test (medias_path, FileTest.EXISTS)) - return; - - string contents; - FileUtils.get_contents (medias_path, out contents); - - int disc_num = int.parse (contents); - media_set.selected_media_number = disc_num; - } - - // TODO: To be removed - private string get_medias_path () throws Error { - var dir = Application.get_medias_dir (); - var uid = uid.get_uid (); - - return @"$dir/$uid.media"; - } - - // TODO: To be removed - private string get_screenshot_path () throws Error { - if (screenshot_path != null) - return screenshot_path; - - var dir = Application.get_snapshots_dir (); - var uid = uid.get_uid (); - var now_time_str = TimeVal ().to_iso8601 (); - screenshot_path = @"$dir/$uid/$now_time_str.png"; - - return screenshot_path; - } - private void save_screenshot (File savestate_dir) throws Error { if (!core.get_can_access_state ()) return; @@ -605,7 +536,11 @@ public class Games.RetroRunner : Object, Runner { if (!core.get_can_access_state ()) return; - var screenshot_path = get_screenshot_path (); + if (game_savestates.length == 0) + return; + + // Load the screenshot of the latest savestate + var screenshot_path = latest_savestate.get_screenshot_path (); if (!FileUtils.test (screenshot_path, FileTest.EXISTS)) return; diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala index 325f2ada..d053ddbc 100644 --- a/src/ui/display-view.vala +++ b/src/ui/display-view.vala @@ -344,17 +344,11 @@ private class Games.DisplayView : Object, UiView { box.runner.pause (); - // FIXME: Temporary hack used to avoid displaying the Quit Dialog when - // not necessary - - var retro_runner = box.runner as RetroRunner; - if (retro_runner != null) { - try { - retro_runner.save (); - } - catch (Error e) { - critical (e.message); - } + try { + box.runner.attempt_create_savestate (); + } + catch (Error e) { + warning (e.message); } if (box.runner.can_quit_safely) { -- GitLab From 17d184602071e4f01d2b8cde7bd1dc22ba64302e Mon Sep 17 00:00:00 2001 From: Yetizone Date: Sun, 2 Jun 2019 20:15:32 +0300 Subject: [PATCH 06/24] application: Remove unused methods referencing old directories --- src/ui/application.vala | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/ui/application.vala b/src/ui/application.vala index 5e08c616..3795364d 100644 --- a/src/ui/application.vala +++ b/src/ui/application.vala @@ -86,20 +86,6 @@ public class Games.Application : Gtk.Application { return @"$data_dir/gnome-games"; } - // TODO: To be removed - public static string get_saves_dir () { - var data_dir = get_data_dir (); - - return @"$data_dir/saves"; - } - - // TODO: To be removed - public static string get_snapshots_dir () { - var data_dir = get_data_dir (); - - return @"$data_dir/snapshots"; - } - public static string get_database_path () { var data_dir = get_data_dir (); @@ -147,13 +133,6 @@ public class Games.Application : Gtk.Application { } } - // TODO: To be removed - public static string get_medias_dir () { - var data_dir = get_data_dir (); - - return @"$data_dir/medias"; - } - public static bool is_running_in_flatpak () { if (is_flatpak != null) return is_flatpak; -- GitLab From 1f1357edaa768d17db331f044f34de1016523158 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 6 Jun 2019 16:16:11 +0300 Subject: [PATCH 07/24] retro-runner: Use temporary savestate --- src/command/command-runner.vala | 2 +- src/core/runner.vala | 2 +- src/dummy/dummy-runner.vala | 2 +- src/retro/retro-runner.vala | 231 +++++++++++++++----------------- src/ui/display-view.vala | 2 +- 5 files changed, 115 insertions(+), 124 deletions(-) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index 03256016..43dcca95 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -28,7 +28,7 @@ public class Games.CommandRunner : Object, Runner { this.args = args; } - public bool check_is_valid (out string error_message) throws Error { + public bool try_init_phase_one (out string error_message) { if (args.length > 0) { error_message = ""; diff --git a/src/core/runner.vala b/src/core/runner.vala index df28e351..f4b9bd68 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -9,7 +9,7 @@ public interface Games.Runner : Object { public abstract MediaSet? media_set { get; } public abstract InputMode input_mode { get; set; } - public abstract bool check_is_valid (out string error_message) throws Error; + public abstract bool try_init_phase_one (out string error_message); public abstract Gtk.Widget get_display (); public abstract Gtk.Widget? get_extra_widget (); public abstract void start () throws Error; diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index b8e697f1..595f3583 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -22,7 +22,7 @@ private class Games.DummyRunner : Object, Runner { set { } } - public bool check_is_valid (out string error_message) throws Error { + public bool try_init_phase_one (out string error_message) { error_message = ""; return true; diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 17b4900e..ad5bc925 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -13,24 +13,7 @@ public class Games.RetroRunner : Object, Runner { } public bool can_resume { - get { - try { - init (); - - // Check if the core can support savestates - if (!core.get_can_access_state ()) - return false; - - // Check if there are any existing savestates - if (game_savestates.length != 0) - return true; - } - catch (Error e) { - warning (e.message); - } - - return false; - } + get { return game_savestates.length != 0; } } private MediaSet _media_set; @@ -60,6 +43,7 @@ public class Games.RetroRunner : Object, Runner { private Title game_title; private Savestate[] game_savestates; private Savestate latest_savestate; + private Savestate tmp_live_savestate; private bool _running; private bool running { @@ -106,10 +90,16 @@ public class Games.RetroRunner : Object, Runner { deinit (); } - public bool check_is_valid (out string error_message) throws Error { + // init_phase_one attempts to init everything that can be init-ed right away + // It is called by the DisplayView to check if a runner can be used + // This method must be called before other methods/properties + public bool try_init_phase_one (out string error_message) { + // Step 1) Check for the two RetroErrors ------------------------------- + // FIXME: Write this properly using RetroCoreManager + /* try { media_set.selected_media_number = 0; - init (); + //init (); } catch (RetroError.MODULE_NOT_FOUND e) { debug (e.message); @@ -123,9 +113,49 @@ public class Games.RetroRunner : Object, Runner { return false; } + */ - error_message = ""; + // Step 2) Load the game's savestates ---------------------------------- + try { + core_source.get_module_path (); // FIXME: Hack needed to get core_id + string core_id = null; + + if (core_descriptor != null) { + core_id = core_descriptor.get_id (); + } + else { + core_id = core_source.get_core_id (); + } + + game_savestates = Savestate.get_game_savestates (uid, core_id); + if (game_savestates.length != 0) + latest_savestate = game_savestates[0]; + } + catch (Error e) { + error_message = e.message; + return false; + } + // Step 3) Init the CoreView ------------------------------------------- + // This is done here such that get_display() won't return null + view = new Retro.CoreView (); + settings.changed["video-filter"].connect (on_video_filter_changed); + on_video_filter_changed (); + + // Step 4) Display the screenshot of the latest_savestate -------------- + // FIXME: This does not work currently, but perhaps we can fix it + // somehow in retro-gtk ? We should be able to render a picture on the + // screen without having to boot the core itself + try { + load_screenshot (); + } + catch (Error e) { + error_message = e.message; + return false; + } + + // Nothing went wrong + error_message = ""; return true; } @@ -141,10 +171,14 @@ public class Games.RetroRunner : Object, Runner { if (latest_savestate != null && latest_savestate.has_media_data ()) media_set.selected_media_number = latest_savestate.get_media_data (); - if (!is_initialized) - init (); + if (!is_initialized) { + if (latest_savestate != null) + tmp_live_savestate = latest_savestate.clone_in_tmp (); + else + tmp_live_savestate = Savestate.create_empty_in_tmp (); - loop.stop (); + init_phase_two (tmp_live_savestate.get_save_directory_path ()); + } if (!is_ready) { if (latest_savestate != null) @@ -159,41 +193,34 @@ public class Games.RetroRunner : Object, Runner { } public void resume () throws Error { - if (!is_initialized) - init (); - - loop.stop (); - - if (!is_ready) { - load_latest_savestate (); + if (is_ready) { + // In this case, the effect is to simply "unpause" an already running game + loop.start (); } + else { + // In this case, the effect is to load the data from a savestate and + // run the game + if (!is_initialized) { + tmp_live_savestate = latest_savestate.clone_in_tmp (); + init_phase_two (tmp_live_savestate.get_save_directory_path ()); + } - loop.start (); - running = true; - } + loop.stop (); - private void load_latest_savestate () throws Error { - // TODO: This method assumes that there exists at least a savestate - // [Yeti]: Perhaps we should bug-proof this using an Assert ? - load_save_ram (latest_savestate.get_save_ram_path ()); - core.reset (); - core.set_state (latest_savestate.get_snapshot_data ()); + // TODO: This will become "load_data_from_arbitrary_savestate ()" + load_data_from_latest_savestate (); - if (latest_savestate.has_media_data ()) - media_set.selected_media_number = latest_savestate.get_media_data (); + loop.start (); + } - is_ready = true; + // In both cases the game is now running + running = true; } - private void init () throws Error { - if (is_initialized) - return; - - view = new Retro.CoreView (); - settings.changed["video-filter"].connect (on_video_filter_changed); - on_video_filter_changed (); - - prepare_core (); + // init_phase_two is used to setup the core, which needs to have the savestate + // in /tmp created and ready + private void init_phase_two (string core_save_directory_path) throws Error { + prepare_core (core_save_directory_path); var present_analog_sticks = input_capabilities == null || input_capabilities.get_allow_analog_gamepads (); input_manager = new RetroInputManager (core, view, present_analog_sticks); @@ -207,23 +234,20 @@ public class Games.RetroRunner : Object, Runner { loop = new Retro.MainLoop (core); running = false; - // Load the game's savestates if there are any - string core_id = null; - - if (core_descriptor != null) { - core_id = core_descriptor.get_id (); - } - else { - core_id = core_source.get_core_id (); - } + is_initialized = true; + } - game_savestates = Savestate.get_game_savestates (uid, core_id); - if (game_savestates.length != 0) - latest_savestate = game_savestates[0]; + private void load_data_from_latest_savestate () throws Error { + // TODO: This method assumes that there exists at least a savestate + // [Yeti]: Perhaps we should bug-proof this using an Assert ? + load_save_ram (latest_savestate.get_save_ram_path ()); + core.reset (); + core.set_state (latest_savestate.get_snapshot_data ()); - load_screenshot (); + if (latest_savestate.has_media_data ()) + media_set.selected_media_number = latest_savestate.get_media_data (); - is_initialized = true; + is_ready = true; } private void deinit () { @@ -252,7 +276,7 @@ public class Games.RetroRunner : Object, Runner { view.set_filter (filter); } - private void prepare_core () throws Error { + private void prepare_core (string save_directory_path) throws Error { string module_path; if (core_descriptor != null) { var module_file = core_descriptor.get_module_file (); @@ -282,11 +306,7 @@ public class Games.RetroRunner : Object, Runner { var platform_id = platform.get_id (); core.system_directory = @"$platforms_dir/$platform_id/system"; - if (latest_savestate != null) { - var save_directory = latest_savestate.get_save_directory_path (); - Application.try_make_dir (save_directory); - core.save_directory = save_directory; - } + core.save_directory = save_directory_path; core.log.connect (Retro.g_log); view.set_core (core); @@ -411,29 +431,28 @@ public class Games.RetroRunner : Object, Runner { } public void attempt_create_savestate () throws Error { + if (!core.get_can_access_state ()) // Check if the core can support savestates + return; + if (!should_save) return; - // Create a new savestate - var game_savestates_dir_path = get_game_savestates_dir_path (); - var now_time_str = TimeVal ().to_iso8601 (); - var new_savestate_path = Path.build_filename (game_savestates_dir_path, now_time_str); - var new_savestate_dir = File.new_for_path (new_savestate_path); + store_save_ram_in_tmp (); + // TODO: Also save the media data in tmp_savestate + tmp_live_savestate.set_snapshot_data (core.get_state ()); + save_screenshot_in_tmp (); - new_savestate_dir.make_directory (); + // Save the tmp_live_savestate into the game savestates directory + var game_savestates_dir_path = get_game_savestates_dir_path (); + tmp_live_savestate.save_in (game_savestates_dir_path); + should_save = false; + /* store_save_ram (new_savestate_dir); if (media_set.get_size () > 1) save_media_data (new_savestate_dir); - - if (!core.get_can_access_state ()) - return; - - save_snapshot (new_savestate_dir); - save_screenshot (new_savestate_dir); - - should_save = false; + */ } private string get_options_path () throws Error { @@ -448,16 +467,13 @@ public class Games.RetroRunner : Object, Runner { return @"$(Config.OPTIONS_DIR)/$options_name.options"; } - private void store_save_ram (File savestate_dir) throws Error{ + private void store_save_ram_in_tmp () throws Error { var bytes = core.get_memory (Retro.MemoryType.SAVE_RAM); var save = bytes.get_data (); if (save.length == 0) return; - var savestate_dir_path = savestate_dir.get_path (); - var save_ram_path = Path.build_filename (savestate_dir_path, "save"); - - FileUtils.set_data (save_ram_path, save); + tmp_live_savestate.set_save_ram_data (save); } private void load_save_ram (string save_ram_path) throws Error { @@ -475,35 +491,12 @@ public class Games.RetroRunner : Object, Runner { core.set_memory (Retro.MemoryType.SAVE_RAM, bytes); } - private void save_snapshot (File savestate_dir) throws Error { - var bytes = core.get_state (); - var buffer = bytes.get_data (); - - var savestate_dir_path = savestate_dir.get_path (); - var snapshot_path = Path.build_filename (savestate_dir_path, "snapshot"); - - FileUtils.set_data (snapshot_path, buffer); - } - - private void save_media_data (File savestate_dir) throws Error { - var savestate_dir_path = savestate_dir.get_path (); - var media_path = Path.build_filename (savestate_dir_path, "media"); - - string contents = media_set.selected_media_number.to_string (); - - FileUtils.set_contents (media_path, contents, contents.length); - } - - private void save_screenshot (File savestate_dir) throws Error { - if (!core.get_can_access_state ()) - return; - + private void save_screenshot_in_tmp () throws Error { var pixbuf = view.get_pixbuf (); if (pixbuf == null) return; - var savestate_dir_path = savestate_dir.get_path (); - var screenshot_path = Path.build_filename (savestate_dir_path, "screenshot"); + var screenshot_path = tmp_live_savestate.get_screenshot_path (); var now = new GLib.DateTime.now_local (); var creation_time = now.to_string (); @@ -532,14 +525,11 @@ public class Games.RetroRunner : Object, Runner { null); } + // Display the screenshot of the latest savestate private void load_screenshot () throws Error { - if (!core.get_can_access_state ()) - return; - if (game_savestates.length == 0) return; - // Load the screenshot of the latest savestate var screenshot_path = latest_savestate.get_screenshot_path (); if (!FileUtils.test (screenshot_path, FileTest.EXISTS)) @@ -567,3 +557,4 @@ public class Games.RetroRunner : Object, Runner { return core; } } + diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala index d053ddbc..65812275 100644 --- a/src/ui/display-view.vala +++ b/src/ui/display-view.vala @@ -228,7 +228,7 @@ private class Games.DisplayView : Object, UiView { try { var runner = game.get_runner (); string error_message; - if (runner.check_is_valid (out error_message)) + if (runner.try_init_phase_one (out error_message)) return runner; reset_display_page (); -- GitLab From cd309d15d4ed807784192caf04c233fb877030c3 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Wed, 3 Jul 2019 21:17:44 +0300 Subject: [PATCH 08/24] retro-runner: Limit the number of automatic savestates --- src/command/command-runner.vala | 10 +- src/core/runner.vala | 4 +- src/dummy/dummy-runner.vala | 10 +- src/retro/retro-runner.vala | 237 ++++++++++++++++++-------------- src/ui/display-view.vala | 23 ++-- 5 files changed, 165 insertions(+), 119 deletions(-) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index 43dcca95..c20664ce 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -82,7 +82,15 @@ public class Games.CommandRunner : Object, Runner { public void stop () { } - public void attempt_create_savestate () { + public bool try_create_savestate (bool is_automatic) { + return false; + } + + public void load_savestate (Savestate savestate) { + } + + public Savestate[] get_savestates () { + return {}; } public InputMode[] get_available_input_modes () { diff --git a/src/core/runner.vala b/src/core/runner.vala index f4b9bd68..f87828f8 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -16,7 +16,9 @@ public interface Games.Runner : Object { public abstract void resume () throws Error; public abstract void pause (); public abstract void stop (); - public abstract void attempt_create_savestate () throws Error; + public abstract bool try_create_savestate (bool is_automatic); + public abstract void load_savestate (Savestate savestate) throws Error; + public abstract Savestate[] get_savestates (); public abstract InputMode[] get_available_input_modes (); public abstract bool key_press_event (Gdk.EventKey event); diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index 595f3583..8434e2ae 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -48,7 +48,15 @@ private class Games.DummyRunner : Object, Runner { public void stop () { } - public void attempt_create_savestate () { + public bool try_create_savestate (bool is_automatic) { + return false; + } + + public void load_savestate (Savestate savestate) { + } + + public Savestate[] get_savestates () { + return {}; } public InputMode[] get_available_input_modes () { diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index ad5bc925..4ddba679 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -94,12 +94,9 @@ public class Games.RetroRunner : Object, Runner { // It is called by the DisplayView to check if a runner can be used // This method must be called before other methods/properties public bool try_init_phase_one (out string error_message) { - // Step 1) Check for the two RetroErrors ------------------------------- - // FIXME: Write this properly using RetroCoreManager - /* try { - media_set.selected_media_number = 0; - //init (); + init_phase_one (); + // TODO: Check for the two RetroErrors using RetroCoreManager } catch (RetroError.MODULE_NOT_FOUND e) { debug (e.message); @@ -113,50 +110,40 @@ public class Games.RetroRunner : Object, Runner { return false; } - */ - - // Step 2) Load the game's savestates ---------------------------------- - try { - core_source.get_module_path (); // FIXME: Hack needed to get core_id - string core_id = null; - - if (core_descriptor != null) { - core_id = core_descriptor.get_id (); - } - else { - core_id = core_source.get_core_id (); - } - - game_savestates = Savestate.get_game_savestates (uid, core_id); - if (game_savestates.length != 0) - latest_savestate = game_savestates[0]; - } catch (Error e) { + debug (e.message); error_message = e.message; + return false; } - // Step 3) Init the CoreView ------------------------------------------- + // Nothing went wrong + error_message = ""; + return true; + } + + private string get_core_id () throws Error { + if (core_descriptor != null) + return core_descriptor.get_id (); + else + return core_source.get_core_id (); + } + + private void init_phase_one () throws Error { + // Step 1) Load the game's savestates ---------------------------------- + game_savestates = Savestate.get_game_savestates (uid, get_core_id ()); + if (game_savestates.length != 0) + latest_savestate = game_savestates[0]; + + // Step 2) Init the CoreView ------------------------------------------- // This is done here such that get_display() won't return null view = new Retro.CoreView (); settings.changed["video-filter"].connect (on_video_filter_changed); on_video_filter_changed (); - // Step 4) Display the screenshot of the latest_savestate -------------- - // FIXME: This does not work currently, but perhaps we can fix it - // somehow in retro-gtk ? We should be able to render a picture on the - // screen without having to boot the core itself - try { - load_screenshot (); - } - catch (Error e) { - error_message = e.message; - return false; - } - - // Nothing went wrong - error_message = ""; - return true; + // Step 3) Display the screenshot of the latest_savestate -------------- + // FIXME: This does not work currently + load_screenshot (); } public Gtk.Widget get_display () { @@ -167,6 +154,33 @@ public class Games.RetroRunner : Object, Runner { return null; } + public void load_savestate (Savestate savestate) throws Error { + stop (); + + tmp_live_savestate = savestate.clone_in_tmp (); + instantiate_core (tmp_live_savestate.get_save_directory_path ()); + + core.save_directory = tmp_live_savestate.get_save_directory_path (); + load_save_ram (savestate.get_save_ram_path ()); + core.set_state (savestate.get_snapshot_data ()); + + if (savestate.has_media_data ()) + media_set.selected_media_number = savestate.get_media_data (); + + loop.start (); + + is_ready = true; + running = true; + } + + public Savestate[] get_savestates () { + if (game_savestates == null) { + critical ("RetroRunner hasn't loaded savestates. Call try_init_phase_one()"); + } + + return game_savestates; + } + public void start () throws Error { if (latest_savestate != null && latest_savestate.has_media_data ()) media_set.selected_media_number = latest_savestate.get_media_data (); @@ -177,7 +191,7 @@ public class Games.RetroRunner : Object, Runner { else tmp_live_savestate = Savestate.create_empty_in_tmp (); - init_phase_two (tmp_live_savestate.get_save_directory_path ()); + instantiate_core (tmp_live_savestate.get_save_directory_path ()); } if (!is_ready) { @@ -192,34 +206,20 @@ public class Games.RetroRunner : Object, Runner { running = true; } - public void resume () throws Error { - if (is_ready) { - // In this case, the effect is to simply "unpause" an already running game - loop.start (); - } - else { - // In this case, the effect is to load the data from a savestate and - // run the game - if (!is_initialized) { - tmp_live_savestate = latest_savestate.clone_in_tmp (); - init_phase_two (tmp_live_savestate.get_save_directory_path ()); - } - - loop.stop (); - - // TODO: This will become "load_data_from_arbitrary_savestate ()" - load_data_from_latest_savestate (); - - loop.start (); + public void resume () { + if (!is_ready) { + critical ("RetroRunner.resume() cannot be called if the game isn't playing"); + return; } - // In both cases the game is now running + // Unpause an already running game + loop.start (); running = true; } - // init_phase_two is used to setup the core, which needs to have the savestate + // instantiate_core is used to setup the core, which needs to have a savestate // in /tmp created and ready - private void init_phase_two (string core_save_directory_path) throws Error { + private void instantiate_core (string core_save_directory_path) throws Error { prepare_core (core_save_directory_path); var present_analog_sticks = input_capabilities == null || input_capabilities.get_allow_analog_gamepads (); @@ -237,19 +237,6 @@ public class Games.RetroRunner : Object, Runner { is_initialized = true; } - private void load_data_from_latest_savestate () throws Error { - // TODO: This method assumes that there exists at least a savestate - // [Yeti]: Perhaps we should bug-proof this using an Assert ? - load_save_ram (latest_savestate.get_save_ram_path ()); - core.reset (); - core.set_state (latest_savestate.get_snapshot_data ()); - - if (latest_savestate.has_media_data ()) - media_set.selected_media_number = latest_savestate.get_media_data (); - - is_ready = true; - } - private void deinit () { if (!is_initialized) return; @@ -340,16 +327,7 @@ public class Games.RetroRunner : Object, Runner { return; pause (); - - try { - attempt_create_savestate (); - } - catch (Error e) { - warning (e.message); - } - deinit (); - stopped (); } @@ -415,44 +393,72 @@ public class Games.RetroRunner : Object, Runner { var data_dir_path = Application.get_data_dir (); var savestates_dir_path = Path.build_filename (data_dir_path, "savestates"); var uid = uid.get_uid (); + var core_id = get_core_id (); + var core_id_prefix = core_id.replace (".libretro", ""); - string core_id = null; + return Path.build_filename (savestates_dir_path, uid + "-" + core_id_prefix); + } - if (core_descriptor != null) { - core_id = core_descriptor.get_id (); - } - else { - core_id = core_source.get_core_id (); + // Returns true/false to let the caller know if the savestate was created successfully + // Currently the caller is the DisplayView + // In the future we might want to throw Errors from here in case there is + // something that can be done, but right now there's nothing we can do if + // savestate creation fails except warn the user of unsaved progress via the + // QuitDialog in the DisplayView + public bool try_create_savestate (bool is_automatic) { + if (!core.get_can_access_state ()) // Check if the core can support savestates + return false; + + try { + create_savestate (is_automatic); } + catch (Error e) { + critical ("RetroRunner failed to create savestate: %s", e.message); - var core_id_prefix = core_id.replace (".libretro", ""); + return false; + } - return Path.build_filename (savestates_dir_path, uid + "-" + core_id_prefix); + return true; // Savestate created successfully } - public void attempt_create_savestate () throws Error { - if (!core.get_can_access_state ()) // Check if the core can support savestates - return; + private void create_savestate (bool is_automatic) throws Error { + // Decide if there are too many automatic savestates and delete the + // first one if so + var nr_automatic_savestates = count_automatic_savestates (); + if (is_automatic) { + var max_nr_automatic_savestates = 5; - if (!should_save) - return; + if (nr_automatic_savestates >= max_nr_automatic_savestates) + delete_first_automatic_savestate (); + } + // Populate the savestate in tmp with data from the current state of the game store_save_ram_in_tmp (); - // TODO: Also save the media data in tmp_savestate + + if (media_set.get_size () > 1) + tmp_live_savestate.set_media_data (media_set); + tmp_live_savestate.set_snapshot_data (core.get_state ()); save_screenshot_in_tmp (); + // Populate the metadata file + var now_time = new DateTime.now (); + var platform_prefix = platform.get_uid_prefix (); + if (is_automatic) + tmp_live_savestate.set_metadata_automatic (now_time, platform_prefix, get_core_id ()); + else { + var nr_manual_savestates = game_savestates.length - nr_automatic_savestates; + var savestate_name = _("New savestate %d").printf (nr_manual_savestates + 1); + + tmp_live_savestate.set_metadata_manual (savestate_name, now_time, platform_prefix, get_core_id ()); + } + // Save the tmp_live_savestate into the game savestates directory var game_savestates_dir_path = get_game_savestates_dir_path (); tmp_live_savestate.save_in (game_savestates_dir_path); should_save = false; - /* - store_save_ram (new_savestate_dir); - - if (media_set.get_size () > 1) - save_media_data (new_savestate_dir); - */ + // FIXME: The game_savestates array should be updated somehow here } private string get_options_path () throws Error { @@ -556,5 +562,28 @@ public class Games.RetroRunner : Object, Runner { public Retro.Core get_core () { return core; } + + private int count_automatic_savestates () { + int counter = 0; + + foreach (var savestate in game_savestates) { + if (savestate.is_automatic ()) + counter++; + } + + return counter; + } + + private void delete_first_automatic_savestate () { + // Delete the first automatic savestate (assume they are sorted + // by creation date for now) + + foreach (var savestate in game_savestates) { + if (savestate.is_automatic ()) { + savestate.delete_from_disk (); + break; + } + } + } } diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala index 65812275..9d2ca269 100644 --- a/src/ui/display-view.vala +++ b/src/ui/display-view.vala @@ -269,10 +269,14 @@ private class Games.DisplayView : Object, UiView { return (Gtk.ResponseType) response; } - private bool try_run_with_cancellable (Runner runner, bool resume, Cancellable cancellable) { + private bool try_run_with_cancellable (Runner runner, bool use_latest_savestate, Cancellable cancellable) { try { - if (resume) - box.runner.resume (); + if (use_latest_savestate) { + var savestates = box.runner.get_savestates (); + var latest_savestate = savestates[0]; + + box.runner.load_savestate (latest_savestate); + } else runner.start (); @@ -344,19 +348,14 @@ private class Games.DisplayView : Object, UiView { box.runner.pause (); - try { - box.runner.attempt_create_savestate (); - } - catch (Error e) { - warning (e.message); - } - - if (box.runner.can_quit_safely) { + if (box.runner.try_create_savestate (true)) { + // Progress saved => can quit game safely box.runner.stop (); - return true; } + // Failed to save progress => warn the user of unsaved progress + // via the QuitDialog if (quit_dialog != null) return false; -- GitLab From 2fad1cd3b1a06bf78b93f500fea43dfe13c93b4b Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 11:05:53 +0300 Subject: [PATCH 09/24] retro-core-source: Call ensure_module_is_found() inside get_core_id() --- src/retro/retro-core-source.vala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/retro/retro-core-source.vala b/src/retro/retro-core-source.vala index 5343bd92..cc0ad491 100644 --- a/src/retro/retro-core-source.vala +++ b/src/retro/retro-core-source.vala @@ -14,6 +14,8 @@ public class Games.RetroCoreSource : Object { } public string get_core_id () throws Error { + ensure_module_is_found (); + return core_descriptor.get_id (); } -- GitLab From ad62af7702d43476581c5f4e9c40e7ed14da9510 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 11:22:43 +0300 Subject: [PATCH 10/24] runner: Add supports_savestates property --- src/command/command-runner.vala | 4 ++++ src/core/runner.vala | 1 + src/dummy/dummy-runner.vala | 4 ++++ src/retro/retro-runner.vala | 4 ++++ 4 files changed, 13 insertions(+) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index c20664ce..469a25de 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -13,6 +13,10 @@ public class Games.CommandRunner : Object, Runner { get { return false; } } + public bool supports_savestates { + get { return false; } + } + public MediaSet? media_set { get { return null; } } diff --git a/src/core/runner.vala b/src/core/runner.vala index f87828f8..c432ddca 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -6,6 +6,7 @@ public interface Games.Runner : Object { public abstract bool can_fullscreen { get; } public abstract bool can_quit_safely { get; } public abstract bool can_resume { get; } + public abstract bool supports_savestates { get; } public abstract MediaSet? media_set { get; } public abstract InputMode input_mode { get; set; } diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index 8434e2ae..3434727c 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -13,6 +13,10 @@ private class Games.DummyRunner : Object, Runner { get { return false; } } + public bool supports_savestates { + get { return false; } + } + public MediaSet? media_set { get { return null; } } diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 4ddba679..432968f5 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -16,6 +16,10 @@ public class Games.RetroRunner : Object, Runner { get { return game_savestates.length != 0; } } + public bool supports_savestates { + get { return core.get_can_access_state (); } + } + private MediaSet _media_set; public MediaSet? media_set { get { return _media_set; } -- GitLab From 9223932ae9e26af559833d6cacb5ad545bbe934b Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 12:08:09 +0300 Subject: [PATCH 11/24] runner: Add can_support_savestates property --- src/command/command-runner.vala | 4 ++++ src/core/runner.vala | 1 + src/dummy/dummy-runner.vala | 4 ++++ src/retro/retro-runner.vala | 4 ++++ 4 files changed, 13 insertions(+) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index 469a25de..9257cc58 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -17,6 +17,10 @@ public class Games.CommandRunner : Object, Runner { get { return false; } } + public bool can_support_savestates { + get { return false; } + } + public MediaSet? media_set { get { return null; } } diff --git a/src/core/runner.vala b/src/core/runner.vala index c432ddca..cbecc5c9 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -7,6 +7,7 @@ public interface Games.Runner : Object { public abstract bool can_quit_safely { get; } public abstract bool can_resume { get; } public abstract bool supports_savestates { get; } + public abstract bool can_support_savestates { get; } // Now or in the future public abstract MediaSet? media_set { get; } public abstract InputMode input_mode { get; set; } diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index 3434727c..c64d1041 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -17,6 +17,10 @@ private class Games.DummyRunner : Object, Runner { get { return false; } } + public bool can_support_savestates { + get { return false; } + } + public MediaSet? media_set { get { return null; } } diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 432968f5..74d97627 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -20,6 +20,10 @@ public class Games.RetroRunner : Object, Runner { get { return core.get_can_access_state (); } } + public bool can_support_savestates { + get { return true; } + } + private MediaSet _media_set; public MediaSet? media_set { get { return _media_set; } -- GitLab From 95ab84b33fff41a60a94fe972f2a949264d141ce Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 12:16:07 +0300 Subject: [PATCH 12/24] runner: Remove the can_quit_safely property --- src/command/command-runner.vala | 4 ---- src/core/runner.vala | 1 - src/dummy/dummy-runner.vala | 4 ---- src/retro/retro-runner.vala | 13 ------------- 4 files changed, 22 deletions(-) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index 9257cc58..e53b3db9 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -5,10 +5,6 @@ public class Games.CommandRunner : Object, Runner { get { return false; } } - public bool can_quit_safely { - get { return true; } - } - public bool can_resume { get { return false; } } diff --git a/src/core/runner.vala b/src/core/runner.vala index cbecc5c9..dc186643 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -4,7 +4,6 @@ public interface Games.Runner : Object { public signal void stopped (); public abstract bool can_fullscreen { get; } - public abstract bool can_quit_safely { get; } public abstract bool can_resume { get; } public abstract bool supports_savestates { get; } public abstract bool can_support_savestates { get; } // Now or in the future diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index c64d1041..df750e48 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -5,10 +5,6 @@ private class Games.DummyRunner : Object, Runner { get { return false; } } - public bool can_quit_safely { - get { return true; } - } - public bool can_resume { get { return false; } } diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 74d97627..ffe8ebbd 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -8,10 +8,6 @@ public class Games.RetroRunner : Object, Runner { get { return true; } } - public bool can_quit_safely { - get { return !should_save; } - } - public bool can_resume { get { return game_savestates.length != 0; } } @@ -58,17 +54,12 @@ public class Games.RetroRunner : Object, Runner { get { return _running; } set { _running = value; - - if (running) - should_save = true; - view.sensitive = running; } } private bool is_initialized; private bool is_ready; - private bool should_save; public RetroRunnerBuilder builder { construct { @@ -88,7 +79,6 @@ public class Games.RetroRunner : Object, Runner { construct { is_initialized = false; is_ready = false; - should_save = false; settings = new Settings ("org.gnome.Games"); } @@ -262,7 +252,6 @@ public class Games.RetroRunner : Object, Runner { _running = false; is_initialized = false; is_ready = false; - should_save = false; } private void on_video_filter_changed () { @@ -464,8 +453,6 @@ public class Games.RetroRunner : Object, Runner { // Save the tmp_live_savestate into the game savestates directory var game_savestates_dir_path = get_game_savestates_dir_path (); tmp_live_savestate.save_in (game_savestates_dir_path); - should_save = false; - // FIXME: The game_savestates array should be updated somehow here } -- GitLab From a218fb8316b9dfca187f81967e0b9663978a00e1 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 12:22:50 +0300 Subject: [PATCH 13/24] runner: Add capture_current_state_pixbuf() --- src/command/command-runner.vala | 3 +++ src/core/runner.vala | 3 +++ src/dummy/dummy-runner.vala | 3 +++ src/retro/retro-runner.vala | 8 +++++++- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index e53b3db9..ddc569e9 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -53,6 +53,9 @@ public class Games.CommandRunner : Object, Runner { return null; } + public void capture_current_state_pixbuf () { + } + public void start () throws Error { string? working_directory = null; string[]? envp = null; diff --git a/src/core/runner.vala b/src/core/runner.vala index dc186643..055214a0 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -17,6 +17,9 @@ public interface Games.Runner : Object { public abstract void resume () throws Error; public abstract void pause (); public abstract void stop (); + + public abstract void capture_current_state_pixbuf (); + public abstract bool try_create_savestate (bool is_automatic); public abstract void load_savestate (Savestate savestate) throws Error; public abstract Savestate[] get_savestates (); diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index df750e48..84fa4c57 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -40,6 +40,9 @@ private class Games.DummyRunner : Object, Runner { return null; } + public void capture_current_state_pixbuf () { + } + public void start () throws Error { } diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index ffe8ebbd..72b6e3d7 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -49,6 +49,8 @@ public class Games.RetroRunner : Object, Runner { private Savestate latest_savestate; private Savestate tmp_live_savestate; + private Gdk.Pixbuf current_state_pixbuf; + private bool _running; private bool running { get { return _running; } @@ -148,6 +150,10 @@ public class Games.RetroRunner : Object, Runner { return view; } + public void capture_current_state_pixbuf () { + current_state_pixbuf = view.get_pixbuf (); + } + public virtual Gtk.Widget? get_extra_widget () { return null; } @@ -493,7 +499,7 @@ public class Games.RetroRunner : Object, Runner { } private void save_screenshot_in_tmp () throws Error { - var pixbuf = view.get_pixbuf (); + var pixbuf = current_state_pixbuf; if (pixbuf == null) return; -- GitLab From ecbcd324d03d16467e6fb080b350125e88538e04 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 12:29:29 +0300 Subject: [PATCH 14/24] runner: Don't throw Errors from resume() --- src/command/command-runner.vala | 2 +- src/core/runner.vala | 5 +++-- src/dummy/dummy-runner.vala | 2 +- src/ui/display-view.vala | 14 ++------------ 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index ddc569e9..cbd7a84c 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -80,7 +80,7 @@ public class Games.CommandRunner : Object, Runner { } } - public void resume () throws Error { + public void resume () { } public void pause () { diff --git a/src/core/runner.vala b/src/core/runner.vala index 055214a0..4504ab2a 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -10,11 +10,12 @@ public interface Games.Runner : Object { public abstract MediaSet? media_set { get; } public abstract InputMode input_mode { get; set; } - public abstract bool try_init_phase_one (out string error_message); public abstract Gtk.Widget get_display (); public abstract Gtk.Widget? get_extra_widget (); + + public abstract bool try_init_phase_one (out string error_message); public abstract void start () throws Error; - public abstract void resume () throws Error; + public abstract void resume (); public abstract void pause (); public abstract void stop (); diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index 84fa4c57..bcc106f1 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -46,7 +46,7 @@ private class Games.DummyRunner : Object, Runner { public void start () throws Error { } - public void resume () throws Error { + public void resume () { } public void pause () { diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala index 9d2ca269..4f8ef415 100644 --- a/src/ui/display-view.vala +++ b/src/ui/display-view.vala @@ -387,12 +387,7 @@ private class Games.DisplayView : Object, UiView { private bool cancel_quitting_game () { if (box.runner != null) - try { - box.runner.resume (); - } - catch (Error e) { - warning (e.message); - } + box.runner.resume (); return false; } @@ -416,12 +411,7 @@ private class Games.DisplayView : Object, UiView { return; if (window.is_active) - try { - box.runner.resume (); - } - catch (Error e) { - warning (e.message); - } + box.runner.resume (); else if (with_delay) focus_out_timeout_id = Timeout.add (FOCUS_OUT_DELAY_MILLISECONDS, on_focus_out_delay_elapsed); else -- GitLab From 993a000ecab493a78c58e86aa9419819b9e257ad Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 13:01:07 +0300 Subject: [PATCH 15/24] runner: Add preview_current_state() --- src/command/command-runner.vala | 3 +++ src/core/runner.vala | 1 + src/dummy/dummy-runner.vala | 3 +++ src/retro/retro-runner.vala | 4 ++++ 4 files changed, 11 insertions(+) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index cbd7a84c..3fb10edf 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -56,6 +56,9 @@ public class Games.CommandRunner : Object, Runner { public void capture_current_state_pixbuf () { } + public void preview_current_state () { + } + public void start () throws Error { string? working_directory = null; string[]? envp = null; diff --git a/src/core/runner.vala b/src/core/runner.vala index 4504ab2a..b704d3a4 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -20,6 +20,7 @@ public interface Games.Runner : Object { public abstract void stop (); public abstract void capture_current_state_pixbuf (); + public abstract void preview_current_state (); public abstract bool try_create_savestate (bool is_automatic); public abstract void load_savestate (Savestate savestate) throws Error; diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index bcc106f1..6565820e 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -43,6 +43,9 @@ private class Games.DummyRunner : Object, Runner { public void capture_current_state_pixbuf () { } + public void preview_current_state () { + } + public void start () throws Error { } diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 72b6e3d7..6801a884 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -154,6 +154,10 @@ public class Games.RetroRunner : Object, Runner { current_state_pixbuf = view.get_pixbuf (); } + public void preview_current_state () { + view.set_pixbuf (current_state_pixbuf); + } + public virtual Gtk.Widget? get_extra_widget () { return null; } -- GitLab From 71c3dbff3869d43bccf57529b6b407cb2f860599 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 13:08:41 +0300 Subject: [PATCH 16/24] runner: Add preview_savestate() --- src/command/command-runner.vala | 3 +++ src/core/runner.vala | 1 + src/dummy/dummy-runner.vala | 3 +++ src/retro/retro-runner.vala | 20 ++++++++++++++++++++ 4 files changed, 27 insertions(+) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index 3fb10edf..cf220943 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -59,6 +59,9 @@ public class Games.CommandRunner : Object, Runner { public void preview_current_state () { } + public void preview_savestate (Savestate savestate) { + } + public void start () throws Error { string? working_directory = null; string[]? envp = null; diff --git a/src/core/runner.vala b/src/core/runner.vala index b704d3a4..abdd6bab 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -21,6 +21,7 @@ public interface Games.Runner : Object { public abstract void capture_current_state_pixbuf (); public abstract void preview_current_state (); + public abstract void preview_savestate (Savestate savestate); public abstract bool try_create_savestate (bool is_automatic); public abstract void load_savestate (Savestate savestate) throws Error; diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index 6565820e..634ace0e 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -46,6 +46,9 @@ private class Games.DummyRunner : Object, Runner { public void preview_current_state () { } + public void preview_savestate (Savestate savestate) { + } + public void start () throws Error { } diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 6801a884..3b14d812 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -45,9 +45,11 @@ public class Games.RetroRunner : Object, Runner { private InputCapabilities input_capabilities; private Settings settings; private Title game_title; + private Savestate[] game_savestates; private Savestate latest_savestate; private Savestate tmp_live_savestate; + private Savestate previewed_savestate; private Gdk.Pixbuf current_state_pixbuf; @@ -158,6 +160,24 @@ public class Games.RetroRunner : Object, Runner { view.set_pixbuf (current_state_pixbuf); } + public void preview_savestate (Savestate savestate) { + previewed_savestate = savestate; + + var screenshot_path = savestate.get_screenshot_path (); + Gdk.Pixbuf pixbuf = null; + + // Treat errors locally because loading the savestate screenshot is not + // a critical operation + try { + pixbuf = new Gdk.Pixbuf.from_file (screenshot_path); + } + catch (Error e) { + warning ("Couldn't load %s: %s", screenshot_path, e.message); + } + + view.set_pixbuf (pixbuf); + } + public virtual Gtk.Widget? get_extra_widget () { return null; } -- GitLab From b6f51f6b5c26154c0df62b14fdb5fbe3d8b685e7 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 14:38:45 +0300 Subject: [PATCH 17/24] retro-runner: Preview the latest savestate when initialising --- src/retro/retro-runner.vala | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 3b14d812..8b8bfd80 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -143,9 +143,15 @@ public class Games.RetroRunner : Object, Runner { settings.changed["video-filter"].connect (on_video_filter_changed); on_video_filter_changed (); - // Step 3) Display the screenshot of the latest_savestate -------------- - // FIXME: This does not work currently - load_screenshot (); + // Step 3) Instantiate the core + // This is needed to check if the core supports savestates + tmp_live_savestate = Savestate.create_empty_in_tmp (); + instantiate_core (tmp_live_savestate.get_save_directory_path ()); + + // Step 4) Preview the latest savestate -------------------------------- + if (latest_savestate != null) + preview_savestate (latest_savestate); + } public Gtk.Widget get_display () { @@ -556,20 +562,6 @@ public class Games.RetroRunner : Object, Runner { null); } - // Display the screenshot of the latest savestate - private void load_screenshot () throws Error { - if (game_savestates.length == 0) - return; - - var screenshot_path = latest_savestate.get_screenshot_path (); - - if (!FileUtils.test (screenshot_path, FileTest.EXISTS)) - return; - - var pixbuf = new Gdk.Pixbuf.from_file (screenshot_path); - view.set_pixbuf (pixbuf); - } - private bool on_shutdown () { stop (); -- GitLab From 29c839933f9bdc2642eab255320fba20a99cdfc6 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 15:16:39 +0300 Subject: [PATCH 18/24] runner: Replace load_savestate() with load_previewed_savestate() --- src/command/command-runner.vala | 14 +++++++------- src/core/runner.vala | 2 +- src/dummy/dummy-runner.vala | 14 +++++++------- src/retro/retro-runner.vala | 24 +++++++++++------------- src/ui/display-view.vala | 3 ++- 5 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index cf220943..6d5d1b94 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -62,6 +62,13 @@ public class Games.CommandRunner : Object, Runner { public void preview_savestate (Savestate savestate) { } + public void load_previewed_savestate () { + } + + public Savestate[] get_savestates () { + return {}; + } + public void start () throws Error { string? working_directory = null; string[]? envp = null; @@ -99,13 +106,6 @@ public class Games.CommandRunner : Object, Runner { return false; } - public void load_savestate (Savestate savestate) { - } - - public Savestate[] get_savestates () { - return {}; - } - public InputMode[] get_available_input_modes () { return { }; } diff --git a/src/core/runner.vala b/src/core/runner.vala index abdd6bab..3dd47682 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -24,7 +24,7 @@ public interface Games.Runner : Object { public abstract void preview_savestate (Savestate savestate); public abstract bool try_create_savestate (bool is_automatic); - public abstract void load_savestate (Savestate savestate) throws Error; + public abstract void load_previewed_savestate () throws Error; public abstract Savestate[] get_savestates (); public abstract InputMode[] get_available_input_modes (); diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index 634ace0e..a3db95c3 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -49,6 +49,13 @@ private class Games.DummyRunner : Object, Runner { public void preview_savestate (Savestate savestate) { } + public void load_previewed_savestate () { + } + + public Savestate[] get_savestates () { + return {}; + } + public void start () throws Error { } @@ -65,13 +72,6 @@ private class Games.DummyRunner : Object, Runner { return false; } - public void load_savestate (Savestate savestate) { - } - - public Savestate[] get_savestates () { - return {}; - } - public InputMode[] get_available_input_modes () { return { }; } diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 8b8bfd80..457298ca 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -184,22 +184,16 @@ public class Games.RetroRunner : Object, Runner { view.set_pixbuf (pixbuf); } - public virtual Gtk.Widget? get_extra_widget () { - return null; - } - - public void load_savestate (Savestate savestate) throws Error { - stop (); - - tmp_live_savestate = savestate.clone_in_tmp (); - instantiate_core (tmp_live_savestate.get_save_directory_path ()); + public void load_previewed_savestate () throws Error { + loop.stop (); + tmp_live_savestate = previewed_savestate.clone_in_tmp (); core.save_directory = tmp_live_savestate.get_save_directory_path (); - load_save_ram (savestate.get_save_ram_path ()); - core.set_state (savestate.get_snapshot_data ()); + load_save_ram (previewed_savestate.get_save_ram_path ()); + core.set_state (previewed_savestate.get_snapshot_data ()); - if (savestate.has_media_data ()) - media_set.selected_media_number = savestate.get_media_data (); + if (previewed_savestate.has_media_data ()) + media_set.selected_media_number = previewed_savestate.get_media_data (); loop.start (); @@ -207,6 +201,10 @@ public class Games.RetroRunner : Object, Runner { running = true; } + public virtual Gtk.Widget? get_extra_widget () { + return null; + } + public Savestate[] get_savestates () { if (game_savestates == null) { critical ("RetroRunner hasn't loaded savestates. Call try_init_phase_one()"); diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala index 4f8ef415..7b0c3ab7 100644 --- a/src/ui/display-view.vala +++ b/src/ui/display-view.vala @@ -275,7 +275,8 @@ private class Games.DisplayView : Object, UiView { var savestates = box.runner.get_savestates (); var latest_savestate = savestates[0]; - box.runner.load_savestate (latest_savestate); + box.runner.preview_savestate (latest_savestate); + box.runner.load_previewed_savestate (); } else runner.start (); -- GitLab From 7028d9b839291172031f5abb351c137196021c37 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 16:51:39 +0300 Subject: [PATCH 19/24] runner: Add delete_savestate() --- src/command/command-runner.vala | 3 +++ src/core/runner.vala | 1 + src/dummy/dummy-runner.vala | 3 +++ src/retro/retro-runner.vala | 12 ++++++++++++ 4 files changed, 19 insertions(+) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index 6d5d1b94..13bd524d 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -106,6 +106,9 @@ public class Games.CommandRunner : Object, Runner { return false; } + public void delete_savestate (Savestate savestate) { + } + public InputMode[] get_available_input_modes () { return { }; } diff --git a/src/core/runner.vala b/src/core/runner.vala index 3dd47682..bd58210a 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -24,6 +24,7 @@ public interface Games.Runner : Object { public abstract void preview_savestate (Savestate savestate); public abstract bool try_create_savestate (bool is_automatic); + public abstract void delete_savestate (Savestate savestate); public abstract void load_previewed_savestate () throws Error; public abstract Savestate[] get_savestates (); diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index a3db95c3..86ae78b5 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -72,6 +72,9 @@ private class Games.DummyRunner : Object, Runner { return false; } + public void delete_savestate (Savestate savestate) { + } + public InputMode[] get_available_input_modes () { return { }; } diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 457298ca..803dc165 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -490,6 +490,18 @@ public class Games.RetroRunner : Object, Runner { // FIXME: The game_savestates array should be updated somehow here } + public void delete_savestate (Savestate savestate) { + Savestate[] new_game_savestates = {}; + + foreach (var existing_savestate in game_savestates) { + if (savestate != existing_savestate) + new_game_savestates += existing_savestate; + } + + game_savestates = new_game_savestates; + savestate.delete_from_disk (); + } + private string get_options_path () throws Error { assert (core != null); -- GitLab From df08e0a6056039dd47496442531e6d82d756f49f Mon Sep 17 00:00:00 2001 From: Yetizone Date: Thu, 8 Aug 2019 17:32:59 +0300 Subject: [PATCH 20/24] runner: Return created savestate from try_create_savestate() --- src/command/command-runner.vala | 4 +- src/core/runner.vala | 4 +- src/dummy/dummy-runner.vala | 4 +- src/retro/retro-runner.vala | 66 ++++++++++++++++++++------------- src/ui/display-view.vala | 2 +- 5 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala index 13bd524d..1f6a57a3 100644 --- a/src/command/command-runner.vala +++ b/src/command/command-runner.vala @@ -102,8 +102,8 @@ public class Games.CommandRunner : Object, Runner { public void stop () { } - public bool try_create_savestate (bool is_automatic) { - return false; + public Savestate? try_create_savestate (bool is_automatic) { + return null; } public void delete_savestate (Savestate savestate) { diff --git a/src/core/runner.vala b/src/core/runner.vala index bd58210a..dbcf52f9 100644 --- a/src/core/runner.vala +++ b/src/core/runner.vala @@ -21,10 +21,10 @@ public interface Games.Runner : Object { public abstract void capture_current_state_pixbuf (); public abstract void preview_current_state (); - public abstract void preview_savestate (Savestate savestate); - public abstract bool try_create_savestate (bool is_automatic); + public abstract Savestate? try_create_savestate (bool is_automatic); public abstract void delete_savestate (Savestate savestate); + public abstract void preview_savestate (Savestate savestate); public abstract void load_previewed_savestate () throws Error; public abstract Savestate[] get_savestates (); diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala index 86ae78b5..6b494162 100644 --- a/src/dummy/dummy-runner.vala +++ b/src/dummy/dummy-runner.vala @@ -68,8 +68,8 @@ private class Games.DummyRunner : Object, Runner { public void stop () { } - public bool try_create_savestate (bool is_automatic) { - return false; + public Savestate? try_create_savestate (bool is_automatic) { + return null; } public void delete_savestate (Savestate savestate) { diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala index 803dc165..d2803edd 100644 --- a/src/retro/retro-runner.vala +++ b/src/retro/retro-runner.vala @@ -158,6 +158,10 @@ public class Games.RetroRunner : Object, Runner { return view; } + public virtual Gtk.Widget? get_extra_widget () { + return null; + } + public void capture_current_state_pixbuf () { current_state_pixbuf = view.get_pixbuf (); } @@ -201,10 +205,6 @@ public class Games.RetroRunner : Object, Runner { running = true; } - public virtual Gtk.Widget? get_extra_widget () { - return null; - } - public Savestate[] get_savestates () { if (game_savestates == null) { critical ("RetroRunner hasn't loaded savestates. Call try_init_phase_one()"); @@ -278,8 +278,13 @@ public class Games.RetroRunner : Object, Runner { game_deinit (); core = null; - view.set_core (null); - view = null; + //view.set_core (null); + + // FIXME: Not sure if requires fixing but: + // This is commented out because otherwise the screen appears freezed + // when loading a savestate + //view = null; + input_manager = null; loop = null; @@ -430,29 +435,27 @@ public class Games.RetroRunner : Object, Runner { return Path.build_filename (savestates_dir_path, uid + "-" + core_id_prefix); } - // Returns true/false to let the caller know if the savestate was created successfully - // Currently the caller is the DisplayView + // Returns the created Savestate or null if the Savestate couldn't be created + // Currently the callers are the DisplayView and the SavestatesList // In the future we might want to throw Errors from here in case there is // something that can be done, but right now there's nothing we can do if // savestate creation fails except warn the user of unsaved progress via the // QuitDialog in the DisplayView - public bool try_create_savestate (bool is_automatic) { + public Savestate? try_create_savestate (bool is_automatic) { if (!core.get_can_access_state ()) // Check if the core can support savestates - return false; + return null; try { - create_savestate (is_automatic); + return create_savestate (is_automatic); } catch (Error e) { critical ("RetroRunner failed to create savestate: %s", e.message); - return false; + return null; } - - return true; // Savestate created successfully } - private void create_savestate (bool is_automatic) throws Error { + private Savestate create_savestate (bool is_automatic) throws Error { // Decide if there are too many automatic savestates and delete the // first one if so var nr_automatic_savestates = count_automatic_savestates (); @@ -460,7 +463,7 @@ public class Games.RetroRunner : Object, Runner { var max_nr_automatic_savestates = 5; if (nr_automatic_savestates >= max_nr_automatic_savestates) - delete_first_automatic_savestate (); + delete_last_automatic_savestate (); } // Populate the savestate in tmp with data from the current state of the game @@ -487,7 +490,22 @@ public class Games.RetroRunner : Object, Runner { // Save the tmp_live_savestate into the game savestates directory var game_savestates_dir_path = get_game_savestates_dir_path (); tmp_live_savestate.save_in (game_savestates_dir_path); - // FIXME: The game_savestates array should be updated somehow here + + // Instantiate the Savestate object + var savestate_path = Path.build_filename (game_savestates_dir_path, now_time.to_string ()); + Savestate savestate = new Savestate (savestate_path); + + // Update the game_savestates array + // Insert the new savestate at the beginning of the array since it's the latest savestate + Savestate[] new_game_savestates = {}; + + new_game_savestates += savestate; + foreach (var existing_savestate in game_savestates) + new_game_savestates += existing_savestate; + + game_savestates = new_game_savestates; + + return savestate; } public void delete_savestate (Savestate savestate) { @@ -601,16 +619,14 @@ public class Games.RetroRunner : Object, Runner { return counter; } - private void delete_first_automatic_savestate () { - // Delete the first automatic savestate (assume they are sorted + private void delete_last_automatic_savestate () { + // Delete the last automatic savestate (assume they are sorted // by creation date for now) + Savestate last_automatic_savestate = null; - foreach (var savestate in game_savestates) { - if (savestate.is_automatic ()) { - savestate.delete_from_disk (); - break; - } - } + foreach (var savestate in game_savestates) + if (savestate.is_automatic ()) + last_automatic_savestate = savestate; } } diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala index 7b0c3ab7..d6102db3 100644 --- a/src/ui/display-view.vala +++ b/src/ui/display-view.vala @@ -349,7 +349,7 @@ private class Games.DisplayView : Object, UiView { box.runner.pause (); - if (box.runner.try_create_savestate (true)) { + if (box.runner.try_create_savestate (true) != null) { // Progress saved => can quit game safely box.runner.stop (); return true; -- GitLab From d9fb6bbf69680b2c78ad3ff8fe421b0d4b6193c6 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Wed, 7 Aug 2019 10:31:03 +0300 Subject: [PATCH 21/24] fullscreen-box: Add autohide property --- src/ui/fullscreen-box.vala | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/ui/fullscreen-box.vala b/src/ui/fullscreen-box.vala index 17553d82..dcb767df 100644 --- a/src/ui/fullscreen-box.vala +++ b/src/ui/fullscreen-box.vala @@ -7,6 +7,33 @@ private class Games.FullscreenBox : Gtk.EventBox, Gtk.Buildable { public bool is_fullscreen { get; set; } + private bool _autohide; + public bool autohide { + get { return _autohide; } + set { + _autohide = value; + + if (value) { + show_ui (); + on_cursor_moved (); + } + else { + // Disable timers + if (ui_timeout_id != -1) { + Source.remove (ui_timeout_id); + ui_timeout_id = -1; + } + + if (cursor_timeout_id != -1) { + Source.remove (cursor_timeout_id); + cursor_timeout_id = -1; + } + + show_cursor (true); + } + } + } + private Gtk.Widget _header_bar; public Gtk.Widget header_bar { get { return _header_bar; } @@ -73,6 +100,9 @@ private class Games.FullscreenBox : Gtk.EventBox, Gtk.Buildable { [GtkCallback] private bool on_motion_event (Gdk.EventMotion event) { + if (!autohide) + return false; + if (event.y_root <= SHOW_HEADERBAR_DISTANCE) show_ui (); -- GitLab From 3e1ec750b91ef73e86f4d69891e651992580c52c Mon Sep 17 00:00:00 2001 From: Yetizone Date: Wed, 7 Aug 2019 18:39:54 +0300 Subject: [PATCH 22/24] ui: Add the SavestateListBoxRow widget --- data/gtk-style.css | 4 ++ data/org.gnome.Games.gresource.xml | 1 + data/ui/savestate-listbox-row.ui | 56 +++++++++++++++++++++++ src/meson.build | 1 + src/ui/savestate-listbox-row.vala | 73 ++++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+) create mode 100644 data/ui/savestate-listbox-row.ui create mode 100644 src/ui/savestate-listbox-row.vala diff --git a/data/gtk-style.css b/data/gtk-style.css index c4cfd62c..e1e66c52 100644 --- a/data/gtk-style.css +++ b/data/gtk-style.css @@ -8,6 +8,10 @@ background-color: @theme_base_color; } +.savestate-row { + padding: 0px; +} + gamesgamethumbnail { background-color: mix (@theme_base_color, @theme_bg_color, 0.5); border-width: 1px; diff --git a/data/org.gnome.Games.gresource.xml b/data/org.gnome.Games.gresource.xml index 2a73870a..91fef297 100644 --- a/data/org.gnome.Games.gresource.xml +++ b/data/org.gnome.Games.gresource.xml @@ -48,6 +48,7 @@ ui/reset-controller-mapping-dialog.ui ui/resume-dialog.ui ui/resume-failed-dialog.ui + ui/savestate-listbox-row.ui ui/search-bar.ui ui/shortcuts-window.ui diff --git a/data/ui/savestate-listbox-row.ui b/data/ui/savestate-listbox-row.ui new file mode 100644 index 00000000..5fc58d0b --- /dev/null +++ b/data/ui/savestate-listbox-row.ui @@ -0,0 +1,56 @@ + + + + + diff --git a/src/meson.build b/src/meson.build index a08f60cf..021e7427 100644 --- a/src/meson.build +++ b/src/meson.build @@ -180,6 +180,7 @@ vala_sources = [ 'ui/reset-controller-mapping-dialog.vala', 'ui/resume-dialog.vala', 'ui/resume-failed-dialog.vala', + 'ui/savestate-listbox-row.vala', 'ui/search-bar.vala', 'ui/shortcuts-window.vala', 'ui/ui-view.vala', diff --git a/src/ui/savestate-listbox-row.vala b/src/ui/savestate-listbox-row.vala new file mode 100644 index 00000000..51b1cdb3 --- /dev/null +++ b/src/ui/savestate-listbox-row.vala @@ -0,0 +1,73 @@ +// This file is part of GNOME Games. License: GPL-3.0+. + +[GtkTemplate (ui = "/org/gnome/Games/ui/savestate-listbox-row.ui")] +private class Games.SavestateListBoxRow : Gtk.ListBoxRow { + [GtkChild] + private Gtk.Image image; + [GtkChild] + private Gtk.Label name_label; + [GtkChild] + private Gtk.Label date_label; + + private Savestate _savestate; + public Savestate savestate { + get { return _savestate; } + set { + _savestate = value; + + if (savestate.is_automatic ()) + name_label.label = _("Autosave"); + else { + name_label.label = savestate.get_name (); + } + + var creation_date = savestate.get_creation_date (); + + /* Translators: this is the day number followed + * by the abbreviated month name followed by the year followed + * by a time in 24h format i.e. "3 Feb 2015 23:04:00" */ + /* xgettext:no-c-format */ + var creation_date_str = creation_date.format (_("%-e %b %Y %X")); + date_label.label = creation_date_str; + + // Load the savestate thumbnail + var screenshot_path = savestate.get_screenshot_path (); + var screenshot_width = 0; + var screenshot_height = 0; + + Gdk.Pixbuf.get_file_info (screenshot_path, out screenshot_width, out screenshot_height); + + var aspect_ratio = ((double) screenshot_width) / screenshot_height; + + // Calculate the thumbnail width and height + const int thumbnail_max_width_height = 64; + var thumbnail_width = screenshot_width; + var thumbnail_height = screenshot_height; + + if (screenshot_width > screenshot_height && screenshot_width != thumbnail_max_width_height) { + thumbnail_width = thumbnail_max_width_height; + thumbnail_height = (int) (thumbnail_max_width_height / aspect_ratio); + } + + if (screenshot_height > screenshot_width && screenshot_height != thumbnail_max_width_height) { + thumbnail_height = thumbnail_max_width_height; + thumbnail_width = (int) (thumbnail_max_width_height * aspect_ratio); + } + + Gdk.Pixbuf thumbnail = null; + + try { + thumbnail = new Gdk.Pixbuf.from_file_at_scale (screenshot_path, thumbnail_width, thumbnail_height, false); + image.set_from_pixbuf (thumbnail); + } + catch (Error e) { + warning ("Failed to load savestate thumbnail: %s", e.message); + } + } + } + + public SavestateListBoxRow (Savestate savestate) { + Object (savestate: savestate); + } +} + -- GitLab From 2816850f56b745d6e57bfe2d7a387bd0529e1c79 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Wed, 7 Aug 2019 18:44:24 +0300 Subject: [PATCH 23/24] ui: Add the SavestatesList widget --- data/gtk-style.css | 10 ++ data/org.gnome.Games.gresource.xml | 1 + data/ui/savestates-list.ui | 76 +++++++++++++ src/meson.build | 2 + src/ui/savestates-list-state.vala | 6 + src/ui/savestates-list.vala | 171 +++++++++++++++++++++++++++++ 6 files changed, 266 insertions(+) create mode 100644 data/ui/savestates-list.ui create mode 100644 src/ui/savestates-list-state.vala create mode 100644 src/ui/savestates-list.vala diff --git a/data/gtk-style.css b/data/gtk-style.css index e1e66c52..410a5f59 100644 --- a/data/gtk-style.css +++ b/data/gtk-style.css @@ -12,6 +12,16 @@ padding: 0px; } +.savestate-thumbnail { + min-width: 64px; + min-height: 64px; + color: rgba(255, 255, 255, 0.5); + background: rgba (0, 0, 0, .5); + border: 1px solid rgba (0, 0, 0, .5); + margin: 6px; + border-radius: 5px; +} + gamesgamethumbnail { background-color: mix (@theme_base_color, @theme_bg_color, 0.5); border-width: 1px; diff --git a/data/org.gnome.Games.gresource.xml b/data/org.gnome.Games.gresource.xml index 91fef297..d157b0a0 100644 --- a/data/org.gnome.Games.gresource.xml +++ b/data/org.gnome.Games.gresource.xml @@ -49,6 +49,7 @@ ui/resume-dialog.ui ui/resume-failed-dialog.ui ui/savestate-listbox-row.ui + ui/savestates-list.ui ui/search-bar.ui ui/shortcuts-window.ui diff --git a/data/ui/savestates-list.ui b/data/ui/savestates-list.ui new file mode 100644 index 00000000..4fca186f --- /dev/null +++ b/data/ui/savestates-list.ui @@ -0,0 +1,76 @@ + + + + + diff --git a/src/meson.build b/src/meson.build index 021e7427..467fb9e4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -181,6 +181,8 @@ vala_sources = [ 'ui/resume-dialog.vala', 'ui/resume-failed-dialog.vala', 'ui/savestate-listbox-row.vala', + 'ui/savestates-list.vala', + 'ui/savestates-list-state.vala', 'ui/search-bar.vala', 'ui/shortcuts-window.vala', 'ui/ui-view.vala', diff --git a/src/ui/savestates-list-state.vala b/src/ui/savestates-list-state.vala new file mode 100644 index 00000000..0915d300 --- /dev/null +++ b/src/ui/savestates-list-state.vala @@ -0,0 +1,6 @@ +private class Games.SavestatesListState : Object { + public signal void load_clicked (); + public signal void delete_clicked (); + + public bool is_revealed { get; set; } +} diff --git a/src/ui/savestates-list.vala b/src/ui/savestates-list.vala new file mode 100644 index 00000000..22429fed --- /dev/null +++ b/src/ui/savestates-list.vala @@ -0,0 +1,171 @@ +// This file is part of GNOME Games. License: GPL-3.0+. + +[GtkTemplate (ui = "/org/gnome/Games/ui/savestates-list.ui")] +private class Games.SavestatesList : Gtk.Box { + [GtkChild] + private Gtk.Revealer revealer; + [GtkChild] + private Gtk.ListBox list_box; + [GtkChild] + private Gtk.ListBoxRow new_savestate_row; + + public bool is_revealed { + get { return revealer.reveal_child; } + set { revealer.reveal_child = value; } + } + + private SavestatesListState _state; + public SavestatesListState state { + get { return _state; } + set { + if (_state != null) + _state.notify["is-revealed"].disconnect (on_state_changed); + + _state = value; + + if (value != null) { + value.notify["is-revealed"].connect (on_state_changed); + value.load_clicked.connect (on_load_clicked); + value.delete_clicked.connect (on_delete_clicked); + } + } + } + + private Runner _runner; + public Runner runner { + get { return _runner; } + set { + _runner = value; + + // Remove current savestate rows + var list_rows = list_box.get_children (); + foreach (var row in list_rows) { + if (row != new_savestate_row) + list_box.remove (row); + } + + if (value == null) + return; + + // value != null + var savestates = _runner.get_savestates (); + foreach (var savestate in savestates) { + var list_row = new SavestateListBoxRow (savestate); + + list_box.add (list_row); + } + } + } + + construct { + list_box.set_header_func (update_header); + } + + [GtkCallback] + private void on_row_activated (Gtk.ListBoxRow activated_row) { + if (activated_row == new_savestate_row) { + var savestate = runner.try_create_savestate (false); + + if (savestate != null) { + var savestate_row = new SavestateListBoxRow (savestate); + + list_box.insert (savestate_row, 1); + select_and_preview_row (savestate_row); + } + else { + // Savestate creation failed + list_box.select_row (list_box.get_row_at_index (1)); + + // TODO: Perhaps we should warn the user that the creation of + // the savestate failed via an in-app notification ? + } + } else { + var savestate_row = activated_row as SavestateListBoxRow; + var savestate = savestate_row.savestate; + + runner.preview_savestate (savestate); + } + } + + private void on_load_clicked () { + if (!try_runner_load_previewed_savestate ()) { + // TODO: Here we could show a dialog with one button like + // "Failed to load savestate [Ok]" + } + + state.is_revealed = false; + } + + private bool try_runner_load_previewed_savestate () { + try { + _runner.load_previewed_savestate (); + } + catch (Error e) { + critical ("Failed to load savestate: %s", e.message); + + return false; + } + + // Nothing went wrong + return true; + } + + private void on_state_changed () { + revealer.reveal_child = state.is_revealed; + + if (state.is_revealed) { + list_box.select_row (null); + runner.capture_current_state_pixbuf (); + runner.pause (); + } + else + runner.resume (); + } + + private void on_delete_clicked () { + var selected_row = list_box.get_selected_row (); + var selected_row_index = selected_row.get_index (); + var savestate_row = selected_row as SavestateListBoxRow; + var savestate = savestate_row.savestate; + + runner.delete_savestate (savestate); + list_box.remove (selected_row); + + // Select and preview a new row + var next_row = list_box.get_row_at_index (selected_row_index); + + if (next_row == null) { // The last row in the list has been deleted + var nr_rows = list_box.get_children ().length (); + + if (nr_rows == 1) { + // The only remaining row in the list is the create savestate one + runner.preview_current_state (); + } + else { + // The last row of the list has been deleted but there are still + // rows remaining in the list + var last_row = list_box.get_row_at_index (selected_row_index - 1); + select_and_preview_row (last_row); + } + + return; + } + + select_and_preview_row (next_row); + } + + private void update_header (Gtk.ListBoxRow row, Gtk.ListBoxRow? before) { + if (before != null && row.get_header () == null) { + var separator = new Gtk.Separator (Gtk.Orientation.HORIZONTAL); + row.set_header (separator); + } + } + + private void select_and_preview_row (Gtk.ListBoxRow row) { + var savestate_row = row as SavestateListBoxRow; + var savestate = savestate_row.savestate; + + list_box.select_row (row); + runner.preview_savestate (savestate); + } +} -- GitLab From a9b47cd3f8c4a566692d086998528c664923b247 Mon Sep 17 00:00:00 2001 From: Yetizone Date: Wed, 7 Aug 2019 19:10:23 +0300 Subject: [PATCH 24/24] ui: Use the SavestatesList widget --- data/ui/display-box.ui | 13 +++- data/ui/display-header-bar.ui | 121 +++++++++++++++++++++++++++++---- src/ui/display-box.vala | 28 +++++++- src/ui/display-header-bar.vala | 71 +++++++++++++++++-- src/ui/display-view.vala | 35 ++++++++-- 5 files changed, 237 insertions(+), 31 deletions(-) diff --git a/data/ui/display-box.ui b/data/ui/display-box.ui index db028f54..7cef4bdf 100644 --- a/data/ui/display-box.ui +++ b/data/ui/display-box.ui @@ -25,8 +25,19 @@ - + True + + + True + True + + + + + True + + display diff --git a/data/ui/display-header-bar.ui b/data/ui/display-header-bar.ui index 61514a69..8474e6af 100644 --- a/data/ui/display-header-bar.ui +++ b/data/ui/display-header-bar.ui @@ -1,12 +1,12 @@ -