From 604d753d1b45341fab7206b515e4a4b89f8df108 Mon Sep 17 00:00:00 2001 From: Christian Moser Date: Sun, 26 Jan 2025 18:36:16 +0100 Subject: [PATCH] 2025.01.26 18:36:15 (desktop) --- sgbackup/archiver/_archiver.py | 6 +- sgbackup/archiver/tarfilearchiver.py | 157 +++++++++++++++++++++++++++ sgbackup/archiver/zipfilearchiver.py | 2 +- sgbackup/gui/_app.py | 3 +- sgbackup/settings.py | 30 +++++ 5 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 sgbackup/archiver/tarfilearchiver.py diff --git a/sgbackup/archiver/_archiver.py b/sgbackup/archiver/_archiver.py index 17958e7..7e9194b 100644 --- a/sgbackup/archiver/_archiver.py +++ b/sgbackup/archiver/_archiver.py @@ -33,12 +33,12 @@ from ..game import Game from ..settings import settings class Archiver(GObject): - def __init__(self,key:str,name:str,extensions:list[str],decription:str|None=None): + def __init__(self,key:str,name:str,extensions:list[str],description:str|None=None): GObject.__init__(self) self.__key = key self.__name = name - if decription: - self.__description = decription + if description: + self.__description = description else: self.__description = "" diff --git a/sgbackup/archiver/tarfilearchiver.py b/sgbackup/archiver/tarfilearchiver.py new file mode 100644 index 0000000..8f5a63c --- /dev/null +++ b/sgbackup/archiver/tarfilearchiver.py @@ -0,0 +1,157 @@ +############################################################################### +# 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 . # +############################################################################### + +from gi.repository.GObject import Property +from gi.repository import GLib + +from ._archiver import Archiver +from tarfile import TarFile +from tempfile import mkdtemp,NamedTemporaryFile +import json +import os +from ..game import Game +import logging +logger = logging.getLogger(__name__) + +class TarfileArchiver(Archiver): + def __init__(self, + key='tarfile', + name="TarFile", + extensions=['.tar'], + description="Archiver for .tar files.", + compression=None): + Archiver.__init__(self,key,name,extensions,description) + if compression is None: + self.__compression="" + else: + self.__compression=compression + + @Property + def compression(self): + return self.__compression + + def is_archive(self, filename): + if (Archiver.is_archive()): + try: + with TarFile(filename,"r:{}".format(self.compression)) as tf: + return ("gameconf.json" in tf.getnames()) + except: + pass + return False + + def do_backup(self, game, filename): + _calc_fraction = lambda n,cnt: ((1.0 / n) * cnt) + + self._backup_progress(game,0.0,"Starting {game} ...".format(game.name)) + files = game.get_backup_files() + + n = len(files) + 2 + cnt=1 + data=json.dumps(game.serialize(),ensure_ascii=False,indent=4) + + with TarFile(filename,'x:{}'.format(self.compression)) as tf: + self._backup_progress(game,_calc_fraction(n,cnt),"gameconf.json") + gcf = os.path.join(GLib.get_tmp_dir(),"sgbackup-" + GLib.get_user_name() + "." + "backup." + game.key + ".gameconf.tmp") + with open(gcf,"wt",encoding="utf-8") as gcfile: + gcfile.write(data) + + tf.add(gcf,"gameconf.json") + + for path,arcname in files: + cnt += 1 + self._backup_progress(game,_calc_fraction(n,cnt),"arcname") + tf.add(path,arcname) + + self._backup_progress(game,1.0,message="Finished ...") + return True + + def do_restore(self,filename): + def rmdir_recursive(dir): + for dirent in os.listdir(dir): + fname = os.path.join(dirent) + + if os.path.islink(fname): + os.unlink(fname) + elif os.path.isdir(fname): + rmdir_recursive(fname) + else: + os.unlink(fname) + os.rmdir(dir) + + if not self.is_archive(filename): + raise RuntimeError("{file} is not a vaild {archiver} archive!".format(file=filename,archiver=self.name)) + + tempdir = mkdtemp(suffix="-sgbackup") + tempfile= os.path.join(tempdir,"gameconf.json") + try: + with TarFile(filename,'r:{}'.format(self.compression)) as tf: + tf.extract("gameconf.json",tempdir) + with open(tempfile,"r",encoding="utf-8") as ifile: + game = Game.new_from_json_file(tempfile) + if not os.path.isdir(game.savegame_root): + os.makedirs(game.savegame_root) + + for arcname in [i for i in tf.getnames() if i not in ("gameconf.json",'.','..')]: + tf.extract(arcname,path=game.savegame_root) + + rmdir_recursive(tempdir) + return True + except Exception as ex: + logger.error("Restoring archive {file} failed! ({message})".format( + file=filename, + message=str(ex))) + + if os.path.isdir(tempdir): + rmdir_recursive(tempdir) + + return False + + +class TarfileBz2Archiver(TarfileArchiver): + def __init__(self): + TarfileArchiver.__init__(self, + 'tarfile-bz2', + "TarfileBzip2", + ['.tbz','.tar.bz2','tar.bzip2'], + "Archiver for bzip2 compressedd tar archives.", + "bz2") + +class TarfileGzArchiver(TarfileArchiver): + def __init__(self): + TarfileArchiver.__init__(self, + 'tarfile-gz', + "TarfileGzip", + ['.tgz','.tar.gz'], + "Archiver for gzip compressed tar archives.", + "gz") + +class TarfileXzArchiver(TarfileArchiver): + def __init__(self): + TarfileArchiver.__init__(self, + 'tarfile-xz', + "TarfileXz", + ['.txz','.tar.xz'], + "Archiver for xz compressed tar archives.", + 'xz') + +ARCHIVERS=[ + TarfileArchiver(), + TarfileBz2Archiver(), + TarfileGzArchiver(), + TarfileXzArchiver(), +] diff --git a/sgbackup/archiver/zipfilearchiver.py b/sgbackup/archiver/zipfilearchiver.py index bf19ad1..0f8ae9b 100644 --- a/sgbackup/archiver/zipfilearchiver.py +++ b/sgbackup/archiver/zipfilearchiver.py @@ -68,7 +68,7 @@ class ZipfileArchiver(Archiver): except: game = zip_game - if not game.savegame_root: + if not os.path.isdir(game.savegame_root): os.makedirs(game.savegame_root) extract_files = [i for i in zf.filelist if i.startswith(zip_game.savegame_dir + "/")] diff --git a/sgbackup/gui/_app.py b/sgbackup/gui/_app.py index b7a2924..3cce714 100644 --- a/sgbackup/gui/_app.py +++ b/sgbackup/gui/_app.py @@ -534,7 +534,7 @@ class AppWindow(Gtk.ApplicationWindow): builder = Gtk.Builder.new() Gtk.ApplicationWindow.__init__(self,**kwargs) - self.set_default_size(800,600) + self.set_default_size(800,700) self.set_icon_name('org.sgbackup.sgbackup-symbolic') self.__builder = builder @@ -561,6 +561,7 @@ class AppWindow(Gtk.ApplicationWindow): self.__vpaned.set_end_child(self.backupview) self.__vpaned.set_resize_start_child(True) self.__vpaned.set_resize_end_child(True) + self.__vpaned.set_position(400) vbox.append(self.__vpaned) diff --git a/sgbackup/settings.py b/sgbackup/settings.py index 6d7a217..f0afe6c 100644 --- a/sgbackup/settings.py +++ b/sgbackup/settings.py @@ -43,6 +43,13 @@ for _zc,_zs in ZIPFILE_COMPRESSION_STR.items(): del _zc del _zs +if sys.platform.lower() == 'win32': + PLATFORM_WINDOWS = True + import winreg +else: + PLATFORM_WINDOWS = False + + class Settings(GObject.GObject): __gtype_name__ = "Settings" @@ -129,6 +136,27 @@ class Settings(GObject.GObject): for v in vars: self.parser.set('variables',v[0],v[1]) + @GObject.Property(type=str) + def steam_installpath(self): + if self.parser.has_section('steam') and self.parser.has_option('installpath'): + return self.parser.get('steam','installdir') + + if PLATFORM_WINDOWS: + for i in ('SOFTWARE\\WOW6432Node\\Valve\\Steam','SOFTWARE\\Valve\\Steam'): + try: + skey = None + skey = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE,i) + svalue = winreg.QueryValueEx(skey,'InstallPath')[0] + if svalue: + self.parser.set('steam','installpath',svalue) + return svalue + except: + continue + finally: + if skey: + skey.Close() + return "" + def add_variable(self,name:str,value:str): self.parser.set('variables',name,value) @@ -149,6 +177,8 @@ class Settings(GObject.GObject): ret.update({ "DOCUMENTS": GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS), "DOCUMENTS_DIR": GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS), + "DATADIR": GLib.get_user_data_dir(), + "STEAM_INSTALLPATH": self.steam_installpath, }) ret.update(self.variables) return ret