🚧 Very unfinished initial work on import undo

This commit is contained in:
GeoffreyCoulaud
2023-07-02 00:08:00 +02:00
parent fabd9828f6
commit d252b6b97d
3 changed files with 82 additions and 7 deletions

View File

@@ -82,6 +82,7 @@ class Game(Gtk.Box):
shared.schema.connect("changed", self.schema_changed)
def update_values(self, data):
shared.store.delete_backup()
for key, value in data.items():
# Convert executables to strings
if key == "executable" and isinstance(value, list):

View File

@@ -93,6 +93,11 @@ class Importer(ErrorProducer):
self.create_dialog()
# Backup the store to enable undo
shared.store.delete_backup()
shared.store.save_backup()
shared.store.protect_backup()
# Collect all errors and reset the cancellables for the managers
# - Only one importer exists at any given time
# - Every import starts fresh
@@ -218,6 +223,7 @@ class Importer(ErrorProducer):
def import_callback(self):
"""Callback called when importing has finished"""
logging.info("Import done")
shared.store.unprotect_backup()
self.import_dialog.close()
self.summary_toast = self.create_summary_toast()
self.create_error_dialog()
@@ -288,13 +294,17 @@ class Importer(ErrorProducer):
"open_preferences",
"import",
)
elif self.n_games_added == 1:
toast.set_title(_("1 game imported"))
elif self.n_games_added > 1:
else:
toast.set_title(
_("1 game imported")
if self.n_games_added == 1
# The variable is the number of games
toast.set_title(_("{} games imported").format(self.n_games_added))
else _("{} games imported").format(self.n_games_added)
)
toast.set_button_label(_("Undo"))
toast.connect(
"button-clicked", self.dialog_response_callback, "undo_import"
)
shared.win.toast_overlay.add_toast(toast)
return toast
@@ -315,5 +325,7 @@ class Importer(ErrorProducer):
self.open_preferences(*args)
elif response == "open_preferences_import":
self.open_preferences(*args).connect("close-request", self.timeout_toast)
elif response == "undo_import":
shared.store.restore_backup()
else:
self.timeout_toast()

View File

@@ -18,6 +18,11 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import logging
from pathlib import Path
from typing import Optional
from shutil import rmtree, copytree
from gi.repository import GLib
from src import shared
from src.game import Game
@@ -33,6 +38,11 @@ class Store:
pipelines: dict[str, Pipeline]
games: dict[str, Game]
games_backup: Optional[dict[str, Game]] = None
covers_backup_path: Optional[Path] = None
is_backup_protected: bool = False
has_backup: bool = False
def __init__(self) -> None:
self.managers = {}
self.pipeline_managers = set()
@@ -104,3 +114,55 @@ class Store:
self.pipelines[game.game_id] = pipeline
pipeline.advance()
return pipeline
def save_backup(self):
"""Save an internal backup of games and covers that can be restored"""
self.games_backup = self.games.copy()
self.covers_backup_path = GLib.dir_make_tmp()
copytree(str(shared.covers_dir), self.covers_backup_path)
def protect_backup(self):
"""Protect the current backup from being deleted"""
self.is_backup_protected = True
def unprotect_backup(self):
"""No longer protect the backup from being deleted"""
self.is_backup_protected = False
def restore_backup(self):
"""Restore the latest backup of games and covers"""
if not self.has_backup:
return
# Remove covers
rmtree(shared.covers_dir)
shared.covers_dir.mkdir()
# Remove games
for game in self.games_backup.values():
game.update_values({"removed": True})
game.save()
shared.win.library.remove_all()
shared.win.hidden_library.remove_all()
# Restore covers
copytree(self.covers_backup_path, str(shared.covers_dir))
# Restore games and covers
for game in self.games_backup.values():
self.add_game(game, {}, run_pipeline=False)
game.save()
game.update()
self.delete_backup()
def delete_backup(self):
"""Delete the latest backup of games and covers (if not protected)"""
if self.is_backup_protected:
return
self.games_backup = None
if self.covers_backup_path and Path(self.covers_backup_path).is_dir():
self.covers_backup_path = None
rmtree(self.covers_backup_path, ignore_errors=True)
self.has_backup = False