class RestoreSettings(object): """Class responsible for restoring settings from a 7z backup file created by BackupSettings. We need bass.dirs initialized to restore the settings, which depends on the bash.ini - so this exports also functions to restore the backed up ini, if it exists. Restoring the settings must be done on boot as soon as we are able to initialize bass#dirs.""" __tmpdir_prefix = u'RestoreSettingsWryeBash_' __unset = object() def __init__(self, settings_file): self._settings_file = GPath(settings_file) self._saved_settings_version = self._settings_saved_with = None # bash.ini handling self._timestamped_old = self.__unset self._bash_ini_path = self.__unset # extract folder self._extract_dir = self.__unset # Restore API ------------------------------------------------------------- def extract_backup(self): """Extract the backup file and return the tmp directory used. If the backup file is a dir we assume it was created by us before restarting.""" if self._settings_file.isfile(): temp_dir = bolt.Path.tempDir( prefix=RestoreSettings.__tmpdir_prefix) archives.extract7z(self._settings_file, temp_dir) self._extract_dir = temp_dir elif self._settings_file.isdir(): self._extract_dir = self._settings_file else: raise BoltError(u'%s is not a valid backup location' % self._settings_file) return self._extract_dir def backup_ini_path(self): """Get the path to the backup bash.ini if it exists - must be run before restore_settings is called, as we need the Bash ini to initialize bass.dirs.""" if self._extract_dir is self.__unset: raise BoltError( u'backup_ini_path: you must extract the settings file first') for r, d, fs in bolt.walkdir(u'%s' % self._extract_dir): for f in fs: if f == u'bash.ini': self._bash_ini_path = jo(r, f) break else: self.bash_ini_path = None return self._bash_ini_path def restore_settings(self, fsName): if self._bash_ini_path is self.__unset: raise BoltError( u'restore_settings: you must handle bash ini first') if self._extract_dir is self.__unset: raise BoltError( u'restore_settings: you must extract the settings file first') try: self._restore_settings(fsName) finally: self.remove_extract_dir(self._extract_dir) def _restore_settings(self, fsName): deprint(u'') deprint(_(u'RESTORE BASH SETTINGS: ') + self._settings_file.s) # backup previous Bash ini if it exists old_bash_ini = dirs['mopy'].join(u'bash.ini') self._timestamped_old = u''.join( [old_bash_ini.root.s, u'(', bolt.timestamp(), u').ini']) try: old_bash_ini.moveTo(self._timestamped_old) deprint(u'Existing bash.ini moved to %s' % self._timestamped_old) except StateError: # does not exist self._timestamped_old = None # restore all the settings files restore_paths = _init_settings_files(fsName).keys() for dest_dir, back_path in restore_paths: full_back_path = self._extract_dir.join(back_path) for name in full_back_path.list(): if full_back_path.join(name).isfile(): deprint( GPath(back_path).join(name).s + u' --> ' + dest_dir.join(name).s) full_back_path.join(name).copyTo(dest_dir.join(name)) # restore savegame profile settings back_path = GPath(u'My Games').join(fsName, u'Saves') saves_dir = dirs['saveBase'].join(u'Saves') full_back_path = self._extract_dir.join(back_path) if full_back_path.exists(): for root_dir, folders, files_ in full_back_path.walk( True, None, True): root_dir = GPath(u'.' + root_dir.s) for name in files_: deprint( back_path.join(root_dir, name).s + u' --> ' + saves_dir.join(root_dir, name).s) full_back_path.join(root_dir, name).copyTo( saves_dir.join(root_dir, name)) # Validation -------------------------------------------------------------- def incompatible_backup_error(self, current_game): saved_settings_version, settings_saved_with = \ self._get_settings_versions() if saved_settings_version > bass.settings['bash.version']: # Disallow restoring settings saved on a newer version of bash # TODO(ut) drop? return u'\n'.join([ _(u'The data format of the selected backup file is newer than ' u'the current Bash version!'), _(u'Backup v%s is not compatible with v%s') % (saved_settings_version, bass.settings['bash.version']), u'', _(u'You cannot use this backup with this version of ' u'Bash.') ]), _(u'Error: Settings are from newer Bash version') else: game_name = self._get_backup_game() if game_name != current_game: return u'\n'.join([ _(u'The selected backup file is for %(game_name)s while ' u'your current game is %(current_game)s') % locals(), _(u'You cannot use this backup with this game.') ]), _(u'Error: Settings are from a different game') return u'', u'' def incompatible_backup_warn(self): saved_settings_version, settings_saved_with = \ self._get_settings_versions() if settings_saved_with != bass.settings['bash.version']: return u'\n'.join([ _(u'The version of Bash used to create the selected backup ' u'file does not match the current Bash version!'), _(u'Backup v%s does not match v%s') % (settings_saved_with, bass.settings['bash.version']), u'', _(u'Do you want to restore this backup anyway?') ]), _(u'Warning: Version Mismatch!') return u'', u'' def _get_settings_versions(self): if self._extract_dir is self.__unset: raise BoltError( u'_get_settings_versions: you must extract the settings file ' u'first') if self._saved_settings_version is None: backup_dat = self._extract_dir.join(u'backup.dat') try: with backup_dat.open('rb') as ins: # version of Bash that created the backed up settings self._saved_settings_version = cPickle.load(ins) # version of Bash that created the backup self._settings_saved_with = cPickle.load(ins) except (OSError, IOError, cPickle.UnpicklingError, EOFError): raise_bolt_error(u'Failed to read %s' % backup_dat) return self._saved_settings_version, self._settings_saved_with def _get_backup_game(self): """Get the game this backup was for - hack, this info belongs to backup.dat.""" for node in os.listdir(u'%s' % self._extract_dir): if node != u'My Games' and not node.endswith( u'Mods') and os.path.isdir(self._extract_dir.join(node).s): return node raise BoltError(u'%s does not contain a game dir' % self._extract_dir) # Dialogs and cleanup/error handling -------------------------------------- @staticmethod def remove_extract_dir(backup_dir): backup_dir.rmtree(safety=RestoreSettings.__tmpdir_prefix) def restore_ini(self): if self._timestamped_old is self.__unset: return # we did not move bash.ini if self._timestamped_old is not None: bolt.deprint(u'Restoring bash.ini') GPath(self._timestamped_old).copyTo(u'bash.ini') elif self._bash_ini_path: # remove bash.ini as it is the one from the backup bolt.GPath(u'bash.ini').remove() @staticmethod def warn_message(balt_, msg=u''): if balt_ is None: return balt_.showWarning( balt_.Link.Frame, u'\n'.join([ _(u'There was an error while trying to restore your settings from ' u'the backup file!'), msg, _(u'No settings were restored.') ]), _(u'Unable to restore backup!'))
def test_permissions(path, permissions='rwcd'): """Test file permissions for a path: r = read permission w = write permission c = file creation permission d = file deletion permission""" return True # Temporarily disabled, for testing purposes path = GPath(path) permissions = permissions.lower() def getTemp(path_): # Get a temp file name if path_.isdir(): tmp = path_.join(u'temp.temp') else: tmp = path_.temp while tmp.exists(): tmp = tmp.temp return tmp def getSmallest(): # Get the smallest file in the directory if path.isfile(): return path smallsize = -1 ret = None for node in path.list(): node = path.join(node) if not node.isfile(): continue node_size = node.size if smallsize == -1 or node_size < smallsize: smallsize = node_size ret = node return ret #--Test read permissions try: smallestFile = None path_exists = path.exists() if 'r' in permissions and path_exists: smallestFile = getSmallest() if smallestFile: with smallestFile.open('rb'): pass #--Test write permissions if 'w' in permissions and path_exists: smallestFile = smallestFile or getSmallest() if smallestFile: with smallestFile.open('ab'): pass #--Test file creation permission (only for directories) if 'c' in permissions: if path.isdir() or not path_exists: if not path_exists: path.makedirs() removeAtEnd = True else: removeAtEnd = False temp = getTemp(path) with temp.open('wb'): pass temp.remove() if removeAtEnd: path.removedirs() #--Test file deletion permission if 'd' in permissions and path_exists: smallestFile = smallestFile or getSmallest() if smallestFile: temp = getTemp(smallestFile) smallestFile.copyTo(temp) smallestFile.remove() temp.moveTo(smallestFile) except Exception as e: if getattr(e, 'errno', 0) == 13: return False # Access denied elif getattr(e, 'winerror', 0) == 183: return False # Cannot create file if already exists else: raise return True