fixed backup of files

This commit is contained in:
Christian Moser 2025-02-12 04:28:33 +01:00
parent f2c188a500
commit 4608f7a6af
Failed to extract signature
7 changed files with 88 additions and 23 deletions

View File

@ -31,6 +31,8 @@ import time
from ..game import Game
from ..settings import settings
from ..utility import sanitize_path,sanitize_windows_path
import logging
logger = logging.getLogger(__name__)
@ -91,7 +93,7 @@ class Archiver(GObject):
dt.strftime("%Y%m%d-%H%M%S"),
"sgbackup",
self.extensions[0][1:] if self.extensions[0].startswith('.') else self.extensions[0]))
return os.path.join(settings.backup_dir,game.savegame_name,game.subdir,basename)
return sanitize_path(os.path.join(settings.backup_dir,game.savegame_name,game.subdir,basename))
def _backup_progress(self,game:Game,fraction:float,message:str|None):
if fraction > 1.0:
@ -242,8 +244,8 @@ class ArchiverManager(GObject):
game_progress._game_progress_connection = self.connect('backup-game-progress',game_progress)
threadpool = {}
if len(game_list) > 8:
n = 8
if len(game_list) > settings.backup_threads:
n = settings.backup_threads
else:
n = len(games)
@ -278,8 +280,8 @@ class ArchiverManager(GObject):
return self.emit('backup',archiver,game,filename)
@Signal(name="backup",return_type=bool,arg_types=(Archiver,Game,str),
flags=SignalFlags.RUN_FIRST,accumulator=signal_accumulator_true_handled)
def backup(self,archiver,game,filename):
flags=SignalFlags.RUN_FIRST)
def do_backup(self,archiver,game,filename):
return True
def is_archive(self,filename)->bool:

View File

@ -20,7 +20,7 @@ from gi.repository.GObject import Property
from gi.repository import GLib
from ._archiver import Archiver
from tarfile import TarFile
from tarfile import open as tf_open, is_tarfile
from tempfile import mkdtemp,NamedTemporaryFile
import json
import os
@ -46,10 +46,11 @@ class TarfileArchiver(Archiver):
return self.__compression
def is_archive(self, filename):
if (Archiver.is_archive()):
if (Archiver.is_archive(self,filename) and is_tarfile(filename)):
try:
with TarFile(filename,"r:{}".format(self.compression)) as tf:
return ("gameconf.json" in tf.getnames())
with tf_open(filename,"r:{}".format(self.compression)) as tf:
#return ("gameconf.json" in tf.getnames())
return True
except:
pass
return False
@ -57,14 +58,14 @@ class TarfileArchiver(Archiver):
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))
self._backup_progress(game,0.0,"Starting {game} ...".format(game=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:
with tf_open(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:
@ -72,7 +73,7 @@ class TarfileArchiver(Archiver):
tf.add(gcf,"gameconf.json")
for path,arcname in files:
for path,arcname in files.items():
cnt += 1
self._backup_progress(game,_calc_fraction(n,cnt),"arcname")
tf.add(path,arcname)
@ -99,7 +100,7 @@ class TarfileArchiver(Archiver):
tempdir = mkdtemp(suffix="-sgbackup")
tempfile= os.path.join(tempdir,"gameconf.json")
try:
with TarFile(filename,'r:{}'.format(self.compression)) as tf:
with tf_open(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)

View File

@ -43,6 +43,7 @@ class ZipfileArchiver(Archiver):
for path,arcname in files.items():
cnt+=1
self._backup_progress(game,_calc_fraction(div,cnt),"{} -> {}".format(game.name,arcname))
print("writing file:",path)
zf.write(path,arcname)
self._backup_progress(game,1.0,"{game} ... FINISHED".format(game=game.name))

View File

@ -1356,7 +1356,6 @@ class Game(GObject):
def get_backup_files(self)->dict[str:str]|None:
def get_backup_files_recursive(sgroot:pathlib.Path,sgdir:str,subdir:str|None=None):
ret = {}
if subdir:
path = sgroot / sgdir / subdir
else:
@ -1366,13 +1365,14 @@ class Game(GObject):
for dirent in os.listdir(path):
file_path = path / dirent
if file_path.is_file():
if subdir:
fname = (os.path.join(subdir,dirent))
fname = os.path.join(subdir,dirent).replace("\\","/")
else:
fname = dirent
if self.game_data.match(fname):
ret[str(path)] = os.path.join(sgdir,fname)
ret[str(str(file_path))] = os.path.join(sgdir,fname)
elif file_path.is_dir():
if subdir:
ret.update(get_backup_files_recursive(sgroot,sgdir,os.path.join(subdir,dirent)))

View File

@ -125,8 +125,16 @@ class SettingsDialog(Gtk.Dialog):
grid.attach(label,0,1,1,1)
page.backup_versions_spinbutton = Gtk.SpinButton.new_with_range(0,1000,1)
page.backup_versions_spinbutton.set_hexpand(True)
page.backup_versions_spinbutton.set_value(settings.backup_versions)
grid.attach(page.backup_versions_spinbutton,1,1,2,1)
label = Gtk.Label.new('Max. Backup Threads:')
page.backup_threads_spinbutton = Gtk.SpinButton.new_with_range(1,256,1)
page.backup_threads_spinbutton.set_hexpand(True)
page.backup_threads_spinbutton.set_value(settings.backup_threads)
grid.attach(label,0,2,1,1)
grid.attach(page.backup_threads_spinbutton,1,2,2,1)
label = Gtk.Label.new("Archiver:")
archiver_model = Gio.ListStore.new(Archiver)
for archiver in ArchiverManager.get_global().archivers.values():
@ -145,8 +153,10 @@ class SettingsDialog(Gtk.Dialog):
if archiver_key == archiver.key:
page.archiver_dropdown.set_selected(i)
break
grid.attach(label,0,2,1,1)
grid.attach(page.archiver_dropdown,1,2,2,1)
grid.attach(label,0,3,1,1)
grid.attach(page.archiver_dropdown,1,3,2,1)
vbox.append(grid)
page.set_child(vbox)
@ -216,13 +226,14 @@ class SettingsDialog(Gtk.Dialog):
try:
dir = dialog.select_folder_finish(result)
if dir is not None:
self.__backupdir_label.set_text(dir.get_path())
self.general_page.backupdir_label.set_text(dir.get_path())
except:
pass
def _on_backupdir_button_clicked(self,button):
dialog = Gtk.FileDialog.new()
dialog.set_title("sgbackup: Choose backup folder")
dialog.set_initial_folder(Gio.File.new_for_path(self.general_page.backupdir_label.get_text()))
dialog.select_folder(self,None,self._on_backupdir_dialog_select_folder)
@ -261,6 +272,7 @@ class SettingsDialog(Gtk.Dialog):
def do_save(self):
settings.backup_dir = self.general_page.backupdir_label.get_text()
settings.backup_versions = self.general_page.backup_versions_spinbutton.get_value_as_int()
settings.backup_threads = self.general_page.backup_threads_spinbutton.get_value_as_int()
settings.archiver = self.general_page.archiver_dropdown.get_selected_item().key
settings.zipfile_compression = self.archiver_page.zf_compressor_dropdown.get_selected_item().compressor
settings.zipfile_compresslevel = self.archiver_page.zf_compresslevel_spinbutton.get_value_as_int()

View File

@ -24,6 +24,8 @@ from gi.repository import GLib,GObject
import zipfile
from threading import RLock
from .utility import sanitize_path
ZIPFILE_COMPRESSION_STR = {
zipfile.ZIP_STORED: "stored",
zipfile.ZIP_DEFLATED: "deflated",
@ -315,19 +317,30 @@ class Settings(GObject.GObject):
@GObject.Property(type=str,nick="backup-dir")
def backup_dir(self)->str:
return self.get_string('sgbackup','backupDirectory',
os.path.join(GLib.get_home_dir(),'SavagameBackups'))
return sanitize_path(self.get_string('sgbackup','backupDirectory',
os.path.join(GLib.get_home_dir(),'SavagameBackups')))
@backup_dir.setter
def backup_dir(self,directory:str):
if not os.path.isabs(directory):
raise ValueError("\"backup_dir\" needs to be an absolute path!")
return self.set_string('sgbackup','backupDirectory',directory)
return self.set_string('sgbackup','backupDirectory',sanitize_path(directory))
@GObject.Property(type=str)
def loglevel(self)->str:
return self.get_string('sgbackup','logLevel',"INFO")
@GObject.Property(type=int)
def backup_threads(self)->int:
return self.get_integer('sgbackup','maxBackupThreads',1)
@backup_threads.setter
def backup_threads(self,max_threads:int):
if (max_threads < 1):
max_threads = 1
self.set_integer('sgbackup','maxBackupThreads',max_threads)
@GObject.Property
def variables(self)->dict[str:str]:
ret = {}
@ -415,7 +428,7 @@ class Settings(GObject.GObject):
@GObject.Property(type=int)
def zipfile_compression(self)->int:
comp = self.parser.has_option('zipfile','compression','deflated')
comp = self.get_string('zipfile','compression','deflated')
try:
return ZIPFILE_STR_COMPRESSION[comp]
except:

36
sgbackup/utility.py Normal file
View File

@ -0,0 +1,36 @@
###############################################################################
# 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/>. #
###############################################################################
import sys
if sys.platform.lower() == 'win32':
PLATFORM_WINDOWS=True
else:
PLATFORM_WINDOWS=False
if sys.platform.lower() in ['linux','freebsd','netbsd','openbsd','dragonfly','macos','cygwin']:
PLATFORM_UNIX = True
else:
PLATFORM_UNIX = False
def sanitize_windows_path(path:str)->str:
return path.replace('/','\\')
def sanitize_path(path:str)->str:
if (PLATFORM_WINDOWS):
return sanitize_windows_path(path)
return path