diff --git a/sgbackup/archiver/_archiver.py b/sgbackup/archiver/_archiver.py index f9d0920..5f8ca51 100644 --- a/sgbackup/archiver/_archiver.py +++ b/sgbackup/archiver/_archiver.py @@ -157,13 +157,6 @@ class ArchiverManager(GObject): def archivers(self): return self.__archivers - def _on_archiver_backup_progress_single(self,game:Game,fraction:float,message:str): - self.emit('backup-game-progress',game,fraction,str) - - def _on_archiver_backup_progress_multi(self,fraction): - self.emit('backup-progress',fraction) - - @Signal(name="backup-game-progress",return_type=None,arg_types=(Game,float,str),flags=SignalFlags.RUN_FIRST) def do_backup_game_progress(self,game,fraction,message): pass @@ -198,7 +191,7 @@ class ArchiverManager(GObject): if not multi_backups: self.emit("backup-progress",fraction) - if self.backup_in_progress: + if not multi_backups and self.backup_in_progress: raise RuntimeError("A backup is already in progress!!!") self.backup_in_progress = True @@ -220,59 +213,79 @@ class ArchiverManager(GObject): self.backup_in_progress = False def backup_many(self,games:list[Game]): - def on_game_progress(game,fraction,message,game_progress): - with game_progress._mutex: + def on_game_progress(archiver,game,fraction,message,game_progress,mutex): + with mutex: game_progress[game.key] = fraction sum_fractions = 0.0 for f in game_progress.values(): sum_fractions += f n = len(game_progress) - self._on_archiver_backup_progress_multi(sum_fractions/n if n > 0 else 0.0) - + progress = ((sum_fractions / n) if n > 0 else 1.0) + if progress < 0.0: + progress = 0.0 + elif progress > 1.0: + progress = 1.0 + + self.emit('backup-progress',progress) def thread_function(game): - archiver = self.standard_archiver self.backup(game,True) + if self.backup_in_progress: raise RuntimeError("A backup is already in progress!!!") self.backup_in_progress = True game_list = list(games) - game_progress = dict(((game.key,0.0) for game in game_list)) - game_progress._mutex = threading.RLock() - game_progress._game_progress_connection = self.connect('backup-game-progress',game_progress) + print(games) + game_progress = dict([(game.key,0.0) for game in game_list]) + mutex = threading.RLock() + + self.__backup_many_game_progress_connection = self.connect('backup-game-progress',on_game_progress,game_progress,mutex) threadpool = {} - if len(game_list) > settings.backup_threads: - n = settings.backup_threads + if settings.backup_threads == 0: + backup_threads = 1 + else: + backup_threads = settings.backup_threads + if len(game_list) > backup_threads: + n = backup_threads else: n = len(games) + print("Starting backup with {n} threads".format(n=n)) + for i in range(n): game=game_list[0] del game_list[0] - thread = threading.Thread(thread_function,args=game,daemon=True) - threadpool.append(thread) + + thread = threading.Thread(target=thread_function,args=(game,),daemon=True) + threadpool[i]=thread thread.start() while threadpool: - time.sleep(0.02) - for i in range(len(threadpool)): + rm_thread=[] + for i in threadpool.keys(): thread = threadpool[i] if thread.is_alive(): continue - del threadpool[i] if game_list: game = game_list[0] del game_list[0] - thread = threading.Thread(thread_function,args=game,daemon=True) - threadpool.append(thread) + thread = threading.Thread(target=thread_function,args=(game,),daemon=True) + threadpool[i] = thread thread.start() + else: + rm_thread.append(i) - self.disconnect(game_progress._game_progress_connection) + for i in rm_thread: + del threadpool[i] + + time.sleep(0.02) + + self.disconnect(self.__backup_many_game_progress_connection) self.emit("backup-finished") self.backup_in_progress = False diff --git a/sgbackup/gui/_app.py b/sgbackup/gui/_app.py index 1ab27e7..3e269d6 100644 --- a/sgbackup/gui/_app.py +++ b/sgbackup/gui/_app.py @@ -31,7 +31,7 @@ from ._gamedialog import GameDialog from ..game import Game,GameManager,SAVEGAME_TYPE_ICONS from ._steam import SteamLibrariesDialog,NewSteamAppsDialog,NoNewSteamAppsDialog from ..steam import Steam -from ._backupdialog import BackupSingleDialog +from ._backupdialog import BackupSingleDialog,BackupManyDialog from ..archiver import ArchiverManager @@ -289,10 +289,10 @@ class GameView(Gtk.Box): if game.is_live and game.is_active and os.path.exists(os.path.join(game.savegame_root,game.savegame_dir)): backup_games.append(game) - # TODO: - #dialog = BackupManyDialog(parent=self.get_root(),games=backup_games) - #dialog.set_modal(False) - #dialog.present() + + dialog = BackupManyDialog(parent=self.get_root(),games=backup_games) + dialog.set_modal(False) + dialog.run() def _on_icon_column_setup(self,factory,item): image = Gtk.Image() diff --git a/sgbackup/gui/_backupdialog.py b/sgbackup/gui/_backupdialog.py index c445d8a..e42dbfc 100644 --- a/sgbackup/gui/_backupdialog.py +++ b/sgbackup/gui/_backupdialog.py @@ -46,12 +46,9 @@ class BackupSingleDialog(Gtk.Dialog): self.__progressbar.set_fraction(0.0) self.get_content_area().append(self.__progressbar) - self.set_modal(False) + self.set_modal(True) self.__ok_button = self.add_button('Close',Gtk.ResponseType.OK) - self.__am_signal_progress = None - self.__am_signal_finished = None - def _on_propgress(self,fraction,message): self.__progressbar.set_text(message if message else "Working ...") @@ -70,11 +67,11 @@ class BackupSingleDialog(Gtk.Dialog): if self.__am_signal_finished is not None: am.disconnect(self.__am_signal_finished) - self.__am_signal_finished = None + del self.__am_signal_finished if self.__am_signal_progress is not None: am.disconnect(self.__am_signal_progress) - self.__am_signal_progress = None + del self.__am_signal_progress #if settings.backup_dialog_close_when_finished: # self.response(Gtk.ResponseType.OK) @@ -97,8 +94,10 @@ class BackupSingleDialog(Gtk.Dialog): am = ArchiverManager.get_global() - self.__am_signal_progress = am.connect('backup-game-progress',self._on_am_backup_game_progress) - self.__am_signal_finished = am.connect('backup-game-finished',self._on_am_backup_game_finished) + 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() @@ -143,7 +142,7 @@ class BackupGameDataSorter(Gtk.Sorter): name1 = item1.game.name.lower() name2 = item2.game.name.lower() - if item1.finsihed: + if item1.finished: if not item2.finished: return Gtk.Ordering.LARGER elif name1 > name2: @@ -166,19 +165,23 @@ class BackupGameDataSorter(Gtk.Sorter): return Gtk.Ordering.EQUAL -class BackupMultiDialog(Gtk.Dialog): +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(False) + self.set_modal(True) + self.set_default_size(640,480) self.__scrolled = Gtk.ScrolledWindow() - self.__games_liststore = Gio.ListStore(BackupGameData) + self.__games_liststore = Gio.ListStore.new(BackupGameData) + self.__games_progress_sorter = BackupGameDataSorter() self.__games_sortmodel = Gtk.SortListModel.new(self.__games_liststore, - BackupGameDataSorter()) + 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) @@ -190,11 +193,13 @@ class BackupMultiDialog(Gtk.Dialog): progress_column = Gtk.ColumnViewColumn.new('Progress',progress_factory) progress_column.set_expand(True) - self.__games_columnview = Gtk.ColumnView.new(self.__games_sortmodel) + 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() @@ -221,18 +226,17 @@ class BackupMultiDialog(Gtk.Dialog): 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) + #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(self,games): - am = ArchiverManager.get_global() + def thread_func(am,games): am.backup_many(games) return 0 @@ -243,26 +247,40 @@ class BackupMultiDialog(Gtk.Dialog): GLib.idle_add(self._on_backup_game_finished,game) def on_am_backup_progress(am,progress): - GLib.idle_add(self._on_backup_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: + print("no games to backup!") + self.desotroy() self.response(Gtk.Response.OK) return - self.present() am = ArchiverManager.get_global() - am.connect('backup-progress',on_am_backup_progress) - am.connect('backup-finished',on_am_backup_finished) - am.connect('backup-game-progress',on_am_backup_game_progress) - am.connect('backup-game-finished',on_am_backup_game_finished) - thread = Thread(target=thread_func,args=(list(self.__games),),daemon=True) + 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) + + print ("Starting thread") + print(self.games) + 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) @@ -291,10 +309,13 @@ class BackupMultiDialog(Gtk.Dialog): 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 @@ -304,11 +325,15 @@ class BackupMultiDialog(Gtk.Dialog): 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 @@ -322,6 +347,20 @@ class BackupMultiDialog(Gtk.Dialog): 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 + return False