diff --git a/sgbackup/game.py b/sgbackup/game.py index 7db46ed..6fd2061 100644 --- a/sgbackup/game.py +++ b/sgbackup/game.py @@ -30,6 +30,7 @@ import sys import logging import pathlib import datetime +from string import Template logger = logging.getLogger(__name__) @@ -783,10 +784,14 @@ class SteamGame(GameData): ignore_match) self.appid = int(appid) self.installdir = installdir + self.librarydir=None def get_variables(self): vars = super().get_variables() vars["INSTALLDIR"] = self.installdir if self.installdir else "" + vars["STEAM_APPID"] = str(self.appid) + vars["STEAM_LIBDIR"] = self.librarydir if self.librarydir else "" + return vars @Property(type=int) def appid(self): @@ -802,12 +807,26 @@ class SteamGame(GameData): def installdir(self,installdir:str|None): self.__installdir = installdir + @Property + def librarydir(self)->str|None: + if not self.__librarydir and self.installdir: + return pathlib.Path(self.installdir).resolve().parent.parent.parent + return self.__librarydir + @librarydir.setter + def librarydir(self,directory): + if not directory: + self.__librarydir = None + elif not os.path.isdir(directory): + raise ValueError("Steam librarydir is not a valid directory!") + self.__librarydir = directory + def serialize(self): ret = super().serialize() ret['appid'] = self.appid if self.installdir: - ret['installdir'] = self.installdir + ret['installdir'] = self.installdir if self.installdir else "" + ret['librarydir'] = self.librarydir if self.librarydir else "" return ret @@ -1015,10 +1034,10 @@ class Game(GObject): @Property(type=str) def dbid(self)->str: - return self.__id + return self.__dbid @dbid.setter def id(self,id:str): - self.__id = id + self.__dbid = id @Property(type=str) def key(self)->str: @@ -1077,8 +1096,12 @@ class Game(GObject): os.path.join(settings.gameconf_dir,'.'.join((self.key,'gameconf'))) return self.__filename + @filename.setter def filename(self,fn:str): + if self.__filename and fn != self.__filename and os.path.isfile(self.__filename): + self.__old_filename = self.__filename + if not os.path.isabs(fn): self.__filename = GLib.build_filename(settings.gameconf_dir,fn) else: @@ -1195,13 +1218,15 @@ class Game(GObject): def savegame_root(self)->str|None: if not self.game_data: return None - return self.game_data.savegame_root + t = Template(self.game_data.savegame_root) + return t.safe_substitute(self.get_variables()) @Property def savegame_dir(self)->str|None: if not self.game_data: return None - return self.game_data.savegame_dir + t = Template(self.game_data.savegame_dir) + return t.safe_substitute(self.get_variables()) def add_variable(self,name:str,value:str): self.__variables[str(name)] = str(value) @@ -1209,15 +1234,20 @@ class Game(GObject): def delete_variable(self,name): if name in self.__variables: del self.__variables[name] - - def get_variable(self,name): - vars = dict(os.environ) - #vars.update(settings.variables) + + def get_variables(self): + vars = settings.get_variables() vars.update(self.__variables) game_data = self.game_data - if (game_data is not None): - vars.update(game_data.variables) - + if game_data is not None: + vars.update(game_data.get_variables()) + return vars + + def get_variable(self,name): + try: + return self.get_variables()[name] + except: + return "" def serialize(self)->dict: ret = { @@ -1253,17 +1283,21 @@ class Game(GObject): return ret def save(self): - old_fname = self.filename - if old_fname: - old_path = pathlib.Path(self.filename).resolve() + path = pathlib.Path(self.filename).resolve() if self.filename else None + if path is None: + logger.error("No filename for saving the game \"{game}\" set! Not saving file!".format(game=self.name)) + return + + if hasattr(self,'__old_filename'): + old_path = pathlib.Path(self.__old_filename).resolve() + if old_path.is_file(): + os.unlink(old_path) + delattr(self,'__old_filename') - new_path = pathlib.Path(settings.gameconf_dir / '.'.join(self.id,'gameconf')).resolve() - if old_fname and (str(old_path) != str(new_path)) and old_path.is_file(): - os.unlink(old_path) - if not new_path.parent.is_dir(): - os.makedirs(new_path.parent) + if not path.parent.is_dir(): + os.makedirs(path.parent) - with open(new_path,'wt',encoding='utf-8') as ofile: + with open(path,'wt',encoding='utf-8') as ofile: ofile.write(json.dumps(self.serialize(),ensure_ascii=False,indent=4)) def __bool__(self): @@ -1271,7 +1305,7 @@ class Game(GObject): def is_backup_file(self,filename:str): pass - + def get_backup_files(self)->dict[str:str]|None: def get_backup_files_recursive(sgroot:pathlib.Path,sgdir:str,subdir:str|None=None): ret = {} @@ -1386,7 +1420,7 @@ class GameManager(GObject): self.add_game(game) def add_game(self,game:Game): - self.__[game.key] = game + self.__games[game.key] = game if (game.steam_macos): self.__steam_games[game.steam_macos.appid] = game self.__steam_macos_games[game.steam_macos.appid] = game diff --git a/sgbackup/gui/_gamedialog.py b/sgbackup/gui/_gamedialog.py index 8a568ea..fa9446e 100644 --- a/sgbackup/gui/_gamedialog.py +++ b/sgbackup/gui/_gamedialog.py @@ -17,7 +17,7 @@ ############################################################################### from gi.repository import Gio,GLib,Gtk,Pango -from gi.repository.GObject import Property,Signal,GObject +from gi.repository.GObject import Property,Signal,GObject,BindingFlags from ..game import ( Game, GameFileMatcher, @@ -1051,8 +1051,8 @@ class GameDialog(Gtk.Dialog): filematch.append(GameFileMatcher(fm_data.match_type,fm_data.match_value)) for i in range(im_model.get_n_items()): - fm_data = im_model.get_item(i) - ignorematch.append(GameFileMatcher(im_data.match_type,fm_data.match_value)) + im_data = im_model.get_item(i) + ignorematch.append(GameFileMatcher(im_data.match_type,im_data.match_value)) for i in range(var_model.get_n_items()): var = var_model.get_item(i) @@ -1092,6 +1092,7 @@ class GameDialog(Gtk.Dialog): self.__game.savegame_type = savegame_type self.__game.savegame_name = savegame_name self.__game.variables = variables + self.__game.filename = '.'.join((self.__game.key(),'gameconf')) else: self.__game = Game(key,name,savegame_name) self.__game.savegame_type = savegame_type @@ -1100,16 +1101,20 @@ class GameDialog(Gtk.Dialog): if self.get_is_valid_savegame_type(SavegameType.WINDOWS): data = get_game_data(self.__windows) installdir = self.__windows.installdir_entry.get_text() - grk_model = self.__windows.lookup_regekys.listview.get_model().get_model() + grk_model = self.__windows.lookup_regkeys.listview.get_model().get_model() irk_model = self.__windows.installdir_regkeys.listview.get_model().get_model() grk = [] irk = [] for i in range(grk_model.get_n_items()): - grk.append(grk_model.get_item(i).regkey) + item = grk.model.get_item(i) + if item.regkey: + grk.append(item.regkey) for i in range(irk_model.get_n_items()): - irk.append(irk_model.get_item(i).regkey) + item = irk.model.get_item(i) + if item.regkey: + irk.append(item.regkey) if self.__game.windows: wg = self.__game.windows @@ -1330,7 +1335,7 @@ class GameDialog(Gtk.Dialog): def _on_filematch_dropdown_selection_changed(self,dropdown,data,item): data = item.get_item() - data.match_type = dropdown.get_slected_item() + data.match_type = dropdown.get_selected_item() def _on_filematch_type_dropdown_setup(self,factory,item): label = Gtk.Label() @@ -1365,7 +1370,7 @@ class GameDialog(Gtk.Dialog): else: label.start_editing() label.grab_focus() - label.bind_property('text',data,'match_value',GObject.BindingFlags.DEFAULT) + label.bind_property('text',data,'match_value',BindingFlags.DEFAULT) label.connect('changed',self._on_filematch_value_label_changed,widget) def _on_windows_regkey_setup(self,factory,item): @@ -1421,7 +1426,7 @@ class GameDialog(Gtk.Dialog): i = 0 while i < model.get_n_items(): item = model.get_item(i) - if not item.match_value: + if not item.match_value.strip(): model.remove(i) continue i += 1 diff --git a/sgbackup/gui/_steam.py b/sgbackup/gui/_steam.py index 94dc4b2..8094b29 100644 --- a/sgbackup/gui/_steam.py +++ b/sgbackup/gui/_steam.py @@ -291,7 +291,7 @@ class NewSteamAppsDialog(Gtk.Dialog): def on_dialog_response(dialog,response): if response == Gtk.ResponseType.APPLY: for i in range(self.__listmodel.get_n_items()): - if data.appid == self.__listmodel.get_item(i): + if data.appid == self.__listmodel.get_item(i).appid: self.__listmodel.remove(i) break @@ -314,6 +314,7 @@ class NewSteamAppsDialog(Gtk.Dialog): dialog = GameDialog(self,game) dialog.set_title("sgbackup: Add Steam Game") + dialog.set_modal(False) dialog.connect('response',on_dialog_response) dialog.present() @@ -331,7 +332,7 @@ class NewSteamAppsDialog(Gtk.Dialog): dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.YES_NO) dialog.set_transient_for(self) - dialog.set_modal(True) + dialog.set_modal(False) dialog.props.text = "Do you want to put \"{steamapp}\" on the ignore list?".format(steamapp=data.name) dialog.props.use_markup = True diff --git a/sgbackup/settings.py b/sgbackup/settings.py index 5d35c62..0f5bab5 100644 --- a/sgbackup/settings.py +++ b/sgbackup/settings.py @@ -105,6 +105,53 @@ class Settings(GObject.GObject): return self.parser.get('sgbackup','logLevel') return "INFO" + @GObject.Property + def variables(self)->dict[str:str]: + ret = {} + if self.parser.has_section('variables'): + for k,v in self.parser.items('variables'): + ret[k] = v + return ret + @variables.setter + def variables(self,vars:dict[str:str]|list|tuple): + if self.parser.has_section('variables'): + for opt in self.parser['variables'].keys(): + self.parser.remove_option('variables',opt) + + if not vars: + return + + if isinstance(vars,dict): + for k,v in vars.items(): + self.parser.set('variables',k,v) + else: + for v in vars: + self.parser.set('variables',v[0],v[1]) + + def add_variable(self,name:str,value:str): + self.parser.set('variables',name,value) + + def remove_variable(self,name:str): + try: + self.parser.remove_option('variables',name) + except: + pass + + def get_variable(self,name:str)->str: + try: + return self.parser.get('variables',name) + except: + return "" + + def get_variables(self)->dict[str:str]: + ret = dict(os.environ) + ret.update({ + "DOCUMENTS": GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS), + "DOCUMENTS_DIR": GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS), + }) + ret.update(self.variables) + return ret + @GObject.Property(type=str) def zipfile_compression(self)->str: if self.parser.has_option('zipfile','compression'):