[gnome-games] migrator: Implement automatic migration
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-games] migrator: Implement automatic migration
- Date: Fri, 9 Aug 2019 13:38:38 +0000 (UTC)
commit 25f9d539ffe273c32b3dae7cde41f16e6679399f
Author: Yetizone <andreii lisita gmail com>
Date: Mon Jul 22 14:30:08 2019 +0300
migrator: Implement automatic migration
src/core/migrator.vala | 270 ++++++++++++++++++++++++++++++++++++++++++++++++
src/meson.build | 1 +
src/ui/application.vala | 5 +
3 files changed, 276 insertions(+)
---
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) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]