gsgbackup/sgbackup/gui/_backupdialog.py

373 lines
14 KiB
Python

###############################################################################
# sgbackup - The SaveGame Backup tool #
# Copyright (C) 2024 Christian Moser #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
###############################################################################
from gi.repository import Gtk,GLib,GObject,Gio
from ..game import GameManager,Game
from ..archiver import ArchiverManager
from ..settings import settings
from threading import Thread,ThreadError
import logging
logger = logging.getLogger(__name__)
class BackupSingleDialog(Gtk.Dialog):
def __init__(self,parent:Gtk.Window,game:Game):
Gtk.Dialog.__init__(self)
self.set_title("sgbackup: Backup -> {game}".format(game=game.name))
self.set_decorated(False)
self.__game = game
self.set_transient_for(parent)
label = Gtk.Label()
label.set_markup("<span size=\"x-large\">Backing up <i>{game}</i></span>".format(
game = GLib.markup_escape_text(game.name)))
self.get_content_area().append(label)
self.__progressbar = Gtk.ProgressBar()
self.__progressbar.set_text("Starting savegame backup ...")
self.__progressbar.set_show_text(True)
self.__progressbar.set_fraction(0.0)
self.get_content_area().append(self.__progressbar)
self.set_modal(True)
self.__ok_button = self.add_button('Close',Gtk.ResponseType.OK)
def _on_propgress(self,fraction,message):
self.__progressbar.set_text(message if message else "Working ...")
self.__progressbar.set_fraction(fraction)
return False
def do_response(self,response):
self.hide()
self.destroy()
def _on_finished(self):
self.__progressbar.set_text("Finished ...")
self.__progressbar.set_fraction(1.0)
self.__ok_button.set_sensitive(True)
am = ArchiverManager.get_global()
if self.__am_signal_finished is not None:
am.disconnect(self.__am_signal_finished)
del self.__am_signal_finished
if self.__am_signal_progress is not None:
am.disconnect(self.__am_signal_progress)
del self.__am_signal_progress
if settings.gui_autoclose_backup_dialog:
self.response(Gtk.ResponseType.OK)
return False
def _on_am_backup_game_progress(self,am,game,fraction,message):
if self.__game.key == game.key:
GLib.idle_add(self._on_propgress,fraction,message)
def _on_am_backup_game_finished(self,am,game):
if self.__game.key == game.key:
GLib.idle_add(self._on_finished)
def run(self):
def _thread_func(archiver_manager,game):
am.backup(game)
self.__ok_button.set_sensitive(False)
self.present()
am = ArchiverManager.get_global()
if not hasattr(self,'__am_signal_progress'):
self.__am_signal_progress = am.connect('backup-game-progress',self._on_am_backup_game_progress)
if not hasattr(self,'__am_signal_finished'):
self.__am_signal_finished = am.connect('backup-game-finished',self._on_am_backup_game_finished)
thread = Thread(target=_thread_func,args=(am,self.__game),daemon=True)
thread.start()
class BackupGameData(GObject.GObject):
def __init__(self,game:Game):
GObject.GObject.__init__(self)
self.__progress = 0.0
self.__game = game
self.__finished = False
@GObject.Property
def game(self)->Game:
return self.__game
@GObject.Property(type=str)
def key(self)->str:
return self.__game.key
@GObject.Property(type=float)
def progress(self)->float:
return self.__progress
@progress.setter
def progress(self,fraction:float):
if fraction < 0.0:
fraction = 0.0
elif fraction > 1.0:
fraction = 1.0
self.__progress = fraction
@GObject.Property(type=bool,nick='finished',default=False)
def finished(self)->bool:
return self.__finished
@finished.setter
def finished(self,is_finished:bool):
self.__finished = is_finished
class BackupGameDataSorter(Gtk.Sorter):
def do_compare(self,item1,item2):
name1 = item1.game.name.lower()
name2 = item2.game.name.lower()
if item1.finished:
if not item2.finished:
return Gtk.Ordering.SMALLER
elif name1 > name2:
return Gtk.Ordering.LARGER
elif name1 < name2:
return Gtk.Ordering.SMALLER
else:
return Gtk.Ordering.EQUAL
elif item2.finished:
return Gtk.Ordering.LARGER
elif item1.progress > item2.progress:
return Gtk.Ordering.SMALLER
elif item1.progress < item2.progress:
return Gtk.Ordering.LARGER
elif name1 > name2:
return Gtk.Ordering.LARGER
elif name1 < name2:
return Gtk.Ordering.SMALLER
else:
return Gtk.Ordering.EQUAL
class BackupManyDialog(Gtk.Dialog):
logger = logger.getChild('BackupMultiDialog')
def __init__(self,parent:Gtk.Window|None=None,games:list[Game]|None=None):
Gtk.Dialog.__init__(self)
if parent:
self.set_transient_for(parent)
self.set_decorated(False)
self.set_modal(True)
self.set_default_size(640,480)
self.__scrolled = Gtk.ScrolledWindow()
self.__games_liststore = Gio.ListStore.new(BackupGameData)
self.__games_progress_sorter = BackupGameDataSorter()
self.__games_sortmodel = Gtk.SortListModel.new(self.__games_liststore,
self.__games_progress_sorter)
self.__games_selection = Gtk.SingleSelection.new(self.__games_sortmodel)
name_factory = Gtk.SignalListItemFactory()
name_factory.connect('setup',self._on_column_name_setup)
name_factory.connect('bind',self._on_column_name_bind)
name_column = Gtk.ColumnViewColumn.new('Name',name_factory)
progress_factory = Gtk.SignalListItemFactory()
progress_factory.connect('setup',self._on_column_progress_setup)
progress_factory.connect('bind',self._on_column_progress_bind)
progress_column = Gtk.ColumnViewColumn.new('Progress',progress_factory)
progress_column.set_expand(True)
self.__games_columnview = Gtk.ColumnView.new(self.__games_selection)
self.__games_columnview.set_vexpand(True)
self.__games_columnview.append_column(name_column)
self.__games_columnview.append_column(progress_column)
self.__scrolled.set_child(self.__games_columnview)
self.__scrolled.set_vexpand(True)
self.get_content_area().append(self.__scrolled)
self.__progressbar = Gtk.ProgressBar()
self.__progressbar.set_hexpand(True)
self.__progressbar.set_show_text(False)
self.get_content_area().append(self.__progressbar)
self.__ok_button = self.add_button("Close",Gtk.ResponseType.OK)
self.games = games
@GObject.Property
def games(self)->list[Game]:
return self.__games
@games.setter
def games(self,games:list[Game]|None):
self.__games = []
if games:
for g in games:
if not isinstance(g,Game):
self.__games = {}
raise TypeError("\"games\" is not an Iterable of \"Game\" instances!")
if g.get_backup_files():
self.__games.append(g)
#if self.__games:
#self.__ok_button.set_sensitive(False)
#else:
#self.__ok_button.set_sensitive(True)
def do_response(self,response):
self.hide()
self.destroy()
def run(self):
def thread_func(am,games):
am.backup_many(games)
return 0
def on_am_backup_game_progress(am,game,progress,message):
GLib.idle_add(self._on_backup_game_progress,game,progress)
def on_am_backup_game_finished(am,game):
GLib.idle_add(self._on_backup_game_finished,game)
def on_am_backup_progress(am,progress):
GLib.idle_add(self._on_backup_progress,progress)
def on_am_backup_finished(am):
GLib.idle_add(self._on_backup_finished)
if not self.games:
logger.warning("No games to backup!")
self.hide()
self.destroy()
self.response(Gtk.Response.OK)
return
self.__ok_button.set_sensitive(False)
am = ArchiverManager.get_global()
if not hasattr(self,'__signal_backup_progress'):
self.__signal_backup_progress = am.connect('backup-progress',
on_am_backup_progress)
if not hasattr(self,'__signal_backup_finished'):
self.__signal_backup_finished = am.connect('backup-finished',
on_am_backup_finished)
if not hasattr(self,'__signal_backup_game_progress'):
self.__signal_backup_game_progress = am.connect('backup-game-progress',
on_am_backup_game_progress)
if not hasattr(self,'__signal_backup_game_finished'):
self.__signal_backup_game_finished = am.connect('backup-game-finished',
on_am_backup_game_finished)
thread = Thread(target=thread_func,args=(am,list(self.__games)),daemon=True)
self.present()
thread.start()
def _on_column_name_setup(self,factory,item):
label = Gtk.Label()
label.set_xalign(0.0)
item.set_child(label)
def _on_column_name_bind(self,factory,item):
label = item.get_child()
data = item.get_item()
label.set_text(data.game.name)
def _on_column_progress_setup(self,factory,item):
progressbar = Gtk.ProgressBar()
progressbar.set_show_text(False)
progressbar.set_hexpand(True)
item.set_child(progressbar)
def _on_column_progress_bind(self,factory,item):
progressbar = item.get_child()
data = item.get_item()
if not hasattr(progressbar,'_property_progress_binding'):
progressbar._property_progress_binding = data.bind_property('progress',progressbar,'fraction',GObject.BindingFlags.SYNC_CREATE)
def _on_backup_game_progress(self,game:Game,progress:float):
for i in reversed(range(self.__games_liststore.get_n_items())):
gamedata = self.__games_liststore.get_item(i)
if gamedata.key == game.key:
gamedata.progress = progress
self.__games_progress_sorter.changed(Gtk.SorterChange.DIFFERENT)
return
gamedata = BackupGameData(game)
gamedata.progress = progress
self.__games_liststore.append(gamedata)
vadjustment = self.__scrolled.get_vadjustment()
vadjustment.set_value(vadjustment.get_upper() - vadjustment.get_page_size())
return False
def _on_backup_game_finished(self,game):
for i in reversed(range(self.__games_liststore.get_n_items())):
gamedata = self.__games_liststore.get_item(i)
if gamedata.key == game.key:
gamedata.progress = 1.0
gamedata.finished = True
self.__games_progress_sorter.changed(Gtk.SorterChange.DIFFERENT)
return
gamedata = BackupGameData(game)
gamedata.progress = 1.0
gamedata.finished = True
self.__games_liststore.append(gamedata)
self.__games_progress_sorter.changed(Gtk.SorterChange.DIFFERENT)
vadjustment = self.__scrolled.get_vadjustment()
vadjustment.set_value(vadjustment.get_upper() - vadjustment.get_page_size())
return False
def _on_backup_progress(self,progress:float):
self.__progressbar.set_fraction(progress)
return False
def _on_backup_finished(self):
self.__progressbar.set_fraction(1.0)
self.__ok_button.set_sensitive(True)
self.set_decorated(True)
am = ArchiverManager.get_global()
if hasattr(self,'__signal_backup_game_finished'):
am.diconnect(self.__signal_backup_game_finished)
del self.__signal_backup_game_finished
if hasattr(self,'__signal_backup_game_progress'):
am.disconnect(self.__signal_backup_game_progress)
del self.__signal_backup_game_progress
if hasattr(self,'__signal_backup_finished'):
am.disconnect(self.__signal_backup_finished)
del self.__signal_backup_finished
if hasattr(self,'__signal_backup_progress'):
am.disconnect(self.__signal_backup_progress)
del self.__signal_backup_progress
if settings.gui_autoclose_backup_dialog:
self.response(Gtk.ResponseType.OK)
return False