diff --git a/sgbackup/archiver/__init__.py b/sgbackup/archiver/__init__.py
index 6196f40..bd8f518 100644
--- a/sgbackup/archiver/__init__.py
+++ b/sgbackup/archiver/__init__.py
@@ -17,9 +17,22 @@
###############################################################################
from ._archiver import Archiver,ArchiverManager
-import importlib
+#import importlib
+import os
-archiver = ArchiverManager()
+_archiver = ArchiverManager.get_global()
+_archiver_path= os.path.dirname(__file__)
+for dirent in os.listdir(_archiver_path):
+ if dirent.startswith('.') or dirent.startswith('_'):
+ continue
+ if dirent.endswith('.py'):
+ module = dirent[0:-3]
+ exec("""
+from . import {module}
+if hasattr({module},"ARCHIVERS"):
+ for a in {module}.ARCHIVERS:
+ _archiver.archivers[a.key] = a
+""".format(module=module))
__ALL__ = [
"Archiver",
diff --git a/sgbackup/archiver/_archiver.py b/sgbackup/archiver/_archiver.py
index c18f2c2..17958e7 100644
--- a/sgbackup/archiver/_archiver.py
+++ b/sgbackup/archiver/_archiver.py
@@ -32,8 +32,9 @@ import time
from ..game import Game
from ..settings import settings
-class Archiver:
+class Archiver(GObject):
def __init__(self,key:str,name:str,extensions:list[str],decription:str|None=None):
+ GObject.__init__(self)
self.__key = key
self.__name = name
if decription:
@@ -59,23 +60,29 @@ class Archiver:
def extensions(self)->list[str]:
return self.__extensions
+ def is_archive(self,filename):
+ for ext in self.extensions:
+ if filename.endswith(ext):
+ return True
+ return False
+
def backup(self,game:Game)->bool:
if not game.get_backup_files():
return
- filename = self.generate_new_backup_filename()
+ filename = self.generate_new_backup_filename(game)
dirname = os.path.dirname(filename)
if not os.path.isdir(dirname):
os.makedirs(dirname)
- self.emit('backup',game,filename)
+ return self.emit('backup',game,filename)
def generate_new_backup_filename(self,game:Game)->str:
dt = datetime.datetime.now()
- basename = '.'.join(game.savegame_name,
+ basename = '.'.join((game.savegame_name,
game.savegame_subdir,
dt.strftime("%Y%m%d-%H%M%S"),
"sgbackup",
- self.extensions[0])
+ self.extensions[0][1:]))
return os.path.join(settings.backup_dir,game.savegame_name,game.subdir,basename)
def _backup_progress(self,game:Game,fraction:float,message:str|None):
@@ -84,7 +91,7 @@ class Archiver:
elif fraction < 0.0:
fraction = 0.0
- self.emit("progress",game,fraction,message)
+ self.emit("backup-progress",game,fraction,message)
@Signal(name="backup",flags=SignalFlags.RUN_FIRST,
@@ -112,7 +119,8 @@ class ArchiverManager(GObject):
def __init__(self):
GObject.__init__(self)
self.__archivers = {}
-
+ self.__backup_in_progress = False
+
@staticmethod
def get_global():
@@ -126,7 +134,18 @@ class ArchiverManager(GObject):
try:
return self.__archivers[settings.archiver]
except:
- return self.__archivers["zipfile"]
+ return self.__archivers["zipfile"]
+
+ @Property(type=bool,nick='backup-in-progress',default=False)
+ def backup_in_progress(self)->bool:
+ return self.__backup_in_progress
+ @backup_in_progress.setter
+ def backup_in_progress(self,b:bool):
+ self.__backup_in_progress = b
+
+ @property
+ def archivers(self):
+ return self.__archivers
def _on_archiver_backup_progress_single(self,archiver,game,fraction,message):
pass
@@ -134,11 +153,12 @@ class ArchiverManager(GObject):
def _on_archiver_backup_progress_multi(self,archiver,game,fraction,message):
pass
+
@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
- @Signal(name="backup-game-finished",return_type=None,arg_types=(Game,float,str),flags=SignalFlags.RUN_FIRST)
+ @Signal(name="backup-game-finished",return_type=None,arg_types=(Game,),flags=SignalFlags.RUN_FIRST)
def do_backup_game_finished(self,game:Game):
pass
@@ -155,21 +175,27 @@ class ArchiverManager(GObject):
self.emit("backup-game-progress",game,fraction,message)
self.emit("backup-progress",fraction)
+ if self.backup_in_progress:
+ raise RuntimeError("A backup is already in progress!!!")
+
+ self.backup_in_progress = True
+
archiver = self.standard_archiver
backup_sc = archiver.connect('backup-progress',on_progress)
archiver.backup(game)
archiver.disconnect(backup_sc)
self.emit("backup-game-finished",game)
- self.emit("backup-finsihed")
+ self.emit("backup-finished")
+ self.backup_in_progress = False
-
-
-
def backup_many(self,games:list[Game]):
def thread_function(game):
archiver = self.standard_archiver
self._on_archiver_backup_progress_multi(archiver,game,1.0,"Finished ...")
+ if self.backup_in_progress:
+ raise RuntimeError("A backup is already in progress!!!")
+ self.backup_in_progress = True
game_list = list(games)
threadpool = {}
@@ -200,5 +226,25 @@ class ArchiverManager(GObject):
thread = threading.Thread(thread_function,args=game,daemon=True)
threadpool.append(thread)
thread.start()
+ self.backup_in_progress = False
+
+ def is_archive(self,filename)->bool:
+ if self.standard_archiver.is_archive(filename):
+ return True
+ for i in self.archivers.values():
+ if i.is_archive(filename):
+ return True
+ return False
+
+ def get_backups(self,game:Game):
+ ret = []
+ for backupdir in [os.path.join(settings.backup_dir,game.savegame_name,i) for i in ('live','finished')]:
+ if os.path.isdir(backupdir):
+ for basename in os.listdir(backupdir):
+ filename = os.path.join(backupdir,basename)
+ if (self.is_archive(filename)):
+ ret.append(filename)
+
+ return ret
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/sgbackup/archiver/zipfilearchiver.py b/sgbackup/archiver/zipfilearchiver.py
index 48f84d4..bf19ad1 100644
--- a/sgbackup/archiver/zipfilearchiver.py
+++ b/sgbackup/archiver/zipfilearchiver.py
@@ -21,7 +21,7 @@ import zipfile
import json
import os
from ..game import Game,GameManager
-from settings import settings
+from ..settings import settings
class ZipfileArchiver(Archiver):
def __init__(self):
@@ -32,7 +32,7 @@ class ZipfileArchiver(Archiver):
self._backup_progress(game,0.0,"Starting {game} ...".format(game=game.name))
files = game.get_backup_files()
- div = len(files + 2)
+ div = len(files) + 2
cnt=1
game_data = json.dumps(game.serialize(),ensure_ascii=False,indent=4)
with zipfile.ZipFile(filename,mode="w",
@@ -45,20 +45,19 @@ class ZipfileArchiver(Archiver):
self._backup_progress(game,_calc_fraction(div,cnt),"{} -> {}".format(game.name,arcname))
zf.write(path,arcname)
- self._backup_progress("{game} ... FINISHED".format(game=game.name))
+ self._backup_progress(game,1.0,"{game} ... FINISHED".format(game=game.name))
def is_archive(self,filename:str)->bool:
if zipfile.is_zipfile(filename):
with zipfile.ZipFile(filename,"r") as zf:
- if 'game.conf' in zf.filelist():
+ if 'gameconf.json' in [i.filename for i in zf.filelist]:
return True
return False
def do_restore(self,filename:str):
# TODO: convert savegame dir if not the same SvaegameType!!!
-
if not zipfile.is_zipfile(filename):
raise RuntimeError("\"{filename}\" is not a valid sgbackup zipfile archive!")
diff --git a/sgbackup/game.py b/sgbackup/game.py
index e75c1ff..48369ad 100644
--- a/sgbackup/game.py
+++ b/sgbackup/game.py
@@ -1151,6 +1151,13 @@ class Game(GObject):
else:
self.__variables = dict(vars)
+ @Property(type=str)
+ def subdir(self):
+ if self.is_live:
+ return "live"
+ else:
+ return "finished"
+
@Property
def game_data(self):
sgtype = self.savegame_type
@@ -1367,7 +1374,10 @@ class Game(GObject):
if self.game_data.match(fname):
ret[str(path)] = os.path.join(sgdir,fname)
elif file_path.is_dir():
- ret.update(get_backup_files_recursive(sgroot,sgdir,os.path.join(subdir,dirent)))
+ if subdir:
+ ret.update(get_backup_files_recursive(sgroot,sgdir,os.path.join(subdir,dirent)))
+ else:
+ ret.update(get_backup_files_recursive(sgroot,sgdir,dirent))
return ret
diff --git a/sgbackup/gui/_app.py b/sgbackup/gui/_app.py
index aa75c25..b7a2924 100644
--- a/sgbackup/gui/_app.py
+++ b/sgbackup/gui/_app.py
@@ -30,6 +30,9 @@ from ._settingsdialog import SettingsDialog
from ._gamedialog import GameDialog
from ..game import Game,GameManager,SAVEGAME_TYPE_ICONS
from ._steam import SteamLibrariesDialog,NewSteamAppsDialog
+from ._backupdialog import BackupSingleDialog
+from ..archiver import ArchiverManager
+
__gtype_name__ = __name__
@@ -236,18 +239,21 @@ class GameView(Gtk.ScrolledWindow):
def _on_actions_column_bind(self,action,item):
child = item.get_child()
game = item.get_item()
-
+ archiver_manager = ArchiverManager.get_global()
child.backup_button.connect('clicked',self._on_columnview_backup_button_clicked,item)
child.edit_button.connect('clicked',self._on_columnview_edit_button_clicked,item)
child.remove_button.connect('clicked',self._on_columnview_remove_button_clicked,item)
+ archiver_manager.bind_property('backup-in-progress',child.backup_button,'sensitive',
+ BindingFlags.SYNC_CREATE,lambda binding,x: False if x else True)
+ archiver_manager.bind_property('backup-in-progress',child.edit_button,'sensitive',
+ BindingFlags.SYNC_CREATE,lambda binding,x: False if x else True)
+ archiver_manager.bind_property('backup-in-progress',child.remove_button,'sensitive',
+ BindingFlags.SYNC_CREATE,lambda binding,x: False if x else True)
def _on_columnview_backup_button_clicked(self,button,item):
- def on_dialog_response(dialog,response):
- dialog.hide()
- dialog.destroy()
-
game = item.get_item()
- print('{}.{}._on_columnview_backup_button_clicked() -> {}'.format(__name__,__class__,game.name))
+ dialog = BackupSingleDialog(self.get_root(),game)
+ dialog.run()
def _on_columnview_edit_button_clicked(self,button,item):
def on_dialog_response(dialog,response):
@@ -303,17 +309,17 @@ class BackupViewData(GObject):
"""
def __init__(self,_game:Game,filename:str):
- GObject.GObject.__init__(self)
+ GObject.__init__(self)
self.__game = _game
self.__filename = filename
basename = os.path.basename(filename)
self.__is_live = (os.path.basename(os.path.dirname(filename)) == 'live')
- parts = filename.split('.')
+ parts = basename.split('.')
self.__savegame_name = parts[0]
- self.__timestamp = DateTime.strptime(parts[1],"%Y%m%d-%H%M%S")
+ self.__timestamp = DateTime.strptime(parts[2],"%Y%m%d-%H%M%S")
- self.__extension = '.' + parts[3:]
+ self.__extension = '.' + '.'.join(parts[3:])
@property
def game(self)->Game:
@@ -410,10 +416,16 @@ class BackupView(Gtk.Box):
timestamp_factory.connect('bind',self._on_timestamp_column_bind)
timestamp_column = Gtk.ColumnViewColumn.new("Timestamp",timestamp_factory)
+ size_factory = Gtk.SignalListItemFactory()
+ size_factory.connect('setup',self._on_size_column_setup)
+ size_factory.connect('bind',self._on_size_column_bind)
+ size_column = Gtk.ColumnViewColumn.new("Size",size_factory)
+
self.__columnview = Gtk.ColumnView.new(selection)
self.__columnview.append_column(live_column)
self.__columnview.append_column(sgname_column)
self.__columnview.append_column(timestamp_column)
+ self.__columnview.append_column(size_column)
self.__columnview.set_vexpand(True)
self.gameview.columnview.connect('activate',self._on_gameview_columnview_activate)
@@ -435,6 +447,7 @@ class BackupView(Gtk.Box):
def _on_live_column_setup(self,factory,item):
checkbutton = Gtk.CheckButton()
checkbutton.set_sensitive(False)
+ item.set_child(checkbutton)
def _on_live_column_bind(self,factory,item):
checkbutton = item.get_child()
@@ -444,7 +457,7 @@ class BackupView(Gtk.Box):
def _on_savegamename_column_setup(self,factory,item):
label = Gtk.Label()
- self.set_child(label)
+ item.set_child(label)
def _on_savegamename_column_bind(self,factory,item):
label = item.get_child()
@@ -490,6 +503,13 @@ class BackupView(Gtk.Box):
self._title_label.set_markup("{}".format(GLib.markup_escape_text(game.name)))
+ self.__liststore.remove_all()
+ for bf in ArchiverManager.get_global().get_backups(game):
+ try:
+ self.__liststore.append(BackupViewData(game,bf))
+ except:
+ pass
+
class AppWindow(Gtk.ApplicationWindow):
diff --git a/sgbackup/gui/_backupdialog.py b/sgbackup/gui/_backupdialog.py
index 12c66a2..2a9fcce 100644
--- a/sgbackup/gui/_backupdialog.py
+++ b/sgbackup/gui/_backupdialog.py
@@ -16,7 +16,68 @@
# along with this program. If not, see . #
###############################################################################
+from gi.repository import Gtk,GLib
from ..game import GameManager,Game
from ..archiver import ArchiverManager
+from threading import Thread,ThreadError
-
+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)
+
+ self.__progressbar = Gtk.ProgressBar()
+ self.__progressbar.set_text("Starting savegame backup ...")
+ self.__progressbar.set_fraction(0.0)
+
+ self.get_content_area().append(self.__progressbar)
+ self.set_modal(False)
+
+ 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 ...")
+ self.__progressbar.set_fraction(fraction)
+ return False
+
+ def _on_finished(self):
+ self.__progressbar.set_text("Finished ...")
+ self.__progressbar.set_fraction(1.0)
+ am = ArchiverManager.get_global()
+ if self.__am_signal_finished is not None:
+ am.disconnect(self.__am_signal_finished)
+ self.__am_signal_finished = None
+
+ if self.__am_signal_progress is not None:
+ am.disconnect(self.__am_signal_progress)
+ self.__am_signal_progress = None
+
+ self.hide()
+ self.destroy()
+
+ 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.present()
+
+ 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)
+ thread = Thread(target=_thread_func,args=(am,self.__game),daemon=True)
+ thread.start()
+
diff --git a/sgbackup/settings.py b/sgbackup/settings.py
index 2ae46dc..6d7a217 100644
--- a/sgbackup/settings.py
+++ b/sgbackup/settings.py
@@ -85,7 +85,8 @@ class Settings(GObject.GObject):
@GObject.Property(type=str,nick="logger-conf")
def logger_conf(self)->str:
return self.__logger_conf
-
+
+
@GObject.Property(type=str,nick="backup-dir")
def backup_dir(self)->str: