🚧 Very unfinished initial work on import undo
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user