def prepareDestinationFolder(self, folder, createNonExistant=True): """Prepare a destination folder for an operation""" if os.path.exists(encode(folder)): self.logger.logmsg('DEBUG', _('Destination folder `%s\' exists') % folder) # folder doesn't exist; try to create if specified elif createNonExistant: try: os.mkdir(encode(folder), 0755) self.logger.logmsg('DEBUG', _("Created destination folder `%s'") % folder) except OSError, error: self.logger.logmsg('ERROR', _("The destination folder `%(a)s' could not be created: %(b)s") % {'a': folder, 'b': error}) return False
def start(self): """One-time backup""" paths = self.parsePaths(self.config) if not paths: self.logger.logmsg("WARNING", _("There are no paths to backup!")) return False if self.options["DestinationType"] == "remote (ssh)": # check if server settings are OK self.checkRemoteServer() if self.options["PkgListsToFile"]: pkgListfiles = self.createPkgLists() else: pkgListfiles = [] if self.options["DiskInfoToFile"]: pkgListfiles.append(self.createDiskInfo()) if not ( self.options["Engine"] == "rsync" and self.options["Incremental"] and not self.options["DestinationType"] == "remote (ssh)" ): if not self.prepareDestinationFolder(self.options["Destination"]): return False if os.path.exists(encode(self.dest)): self.logger.logmsg("WARNING", _("`%s' exists and will be overwritten.") % self.dest) shutil_modded.rmtree(encode(self.dest), onerror=self.onError) self.ifCancel() command = self.parseCommand(self.config) self.addListFilesToBackup(pkgListfiles, command, self.options["Engine"], paths) # Now that the paths & commands are set up... retval = self.backupPaths(paths, command) self.deleteListFiles(pkgListfiles) self.ifCancel() if self.options["DestinationType"] == "local": try: os.chmod(self.dest, 0711) except: pass # All done! self.logger.logmsg("INFO", _("Finished one-time backup")) return retval
def start(self): """One-time backup""" paths = self.parsePaths(self.config) if not paths: self.logger.logmsg('WARNING', _('There are no paths to backup!')) return False if self.options['DestinationType'] == 'remote (ssh)': # check if server settings are OK self.checkRemoteServer() if self.options['PkgListsToFile']: pkgListfiles = self.createPkgLists() else: pkgListfiles = [] if self.options['DiskInfoToFile']: pkgListfiles.append(self.createDiskInfo()) if not (self.options['Engine'] == 'rsync' and self.options['Incremental'] and \ not self.options['DestinationType'] == 'remote (ssh)'): if not self.prepareDestinationFolder(self.options['Destination']): return False if os.path.exists(encode(self.dest)): self.logger.logmsg('WARNING', _('`%s\' exists and will be overwritten.') % self.dest) shutil_modded.rmtree(encode(self.dest), onerror=self.onError) self.ifCancel() command = self.parseCommand() self.addListFilesToBackup(pkgListfiles, command, self.options['Engine'], paths) # Now that the paths & commands are set up... retval = self.backupPaths(paths, command) self.deleteListFiles(pkgListfiles) self.ifCancel() if self.options['DestinationType'] == 'local': try: os.chmod(self.dest, 0711) except: pass # All done! self.logger.logmsg('INFO', _('Finished one-time backup')) return retval
def removeOldBackups(self): """Get list of old backups and remove them""" # get listing, local or remote if self.options["DestinationType"] == "remote (ssh)": client, sftpClient = sftp.connect( self.options["RemoteHost"], self.options["RemoteUsername"], self.options["RemotePassword"], self.options["RemotePort"], ) listing = sftpClient.listdir(self.options["RemoteFolder"]) else: listing = os.listdir(encode(self.options["Destination"])) # Normalize the unicode strings storing the filenames. This fixes a problem # on HFS+ filesystems where supplying a set name on the command line # resulted in a different Unicode string than the filename of the set. listing = [decode(i, filename=True) for i in listing] listing.sort() oldbackups = [] for i in listing: backupPrefix = "%s-%s-" % (_("Backup"), self.config.getSetName()) if i.startswith(backupPrefix): oldbackups.append(i) # ...And remove them. oldbackups.reverse() if self.options["DestinationType"] == "remote (ssh)": for i in oldbackups[self.options["OldToKeep"] :]: remoteBackup = os.path.join(self.options["RemoteFolder"], i) self.logger.logmsg( "DEBUG", _("Removing old backup `%(a)s' on %(b)s") % {"a": remoteBackup, "b": self.options["RemoteHost"]}, ) sftp.remove(sftpClient, remoteBackup) sftpClient.close() client.close() else: if self.options["Engine"] == "rsync" and self.options["Incremental"] and oldbackups: for i in oldbackups[:-1]: self.logger.logmsg("DEBUG", _("Removing old backup `%s'") % i) path = os.path.join(self.options["Destination"], i) shutil_modded.rmtree(encode(path), onerror=self.onError) oldIncrementalBackup = os.path.join(self.options["Destination"], oldbackups[-1]) if ( not oldIncrementalBackup.endswith(".tar") and not oldIncrementalBackup.endswith(".tar.gz") and not oldIncrementalBackup.endswith(".tar.bz2") ): # oldIncrementalBackup = rsync self.logger.logmsg("DEBUG", _("Moving `%s' to `%s'") % (oldIncrementalBackup, self.dest)) shutil_modded.move(encode(oldIncrementalBackup), encode(self.dest)) else: # source = is not a rsync backup - remove it and start fresh self.logger.logmsg("DEBUG", _("`%s' is not an rsync backup - removing.") % oldIncrementalBackup) shutil_modded.rmtree(encode(oldIncrementalBackup), onerror=self.onError) else: for i in oldbackups[self.options["OldToKeep"] :]: self.logger.logmsg("DEBUG", _("Removing old backup `%s'") % i) path = os.path.join(self.options["Destination"], i) shutil_modded.rmtree(encode(path), onerror=self.onError)
def __init__(self, name, level=logging.DEBUG): """Setup the fwbackups logger, text mode""" logging.Logger.__init__(self, name, level) self.__printToo = False self.__functions = [] self.__newmessages = False try: # need a handler loghandler = logging.FileHandler(encode(LOGLOC), 'a') # Create formatter & add formatter to handler logformatter = logging.Formatter("%(message)s") loghandler.setFormatter(logformatter) # add handler to logger self.addHandler(loghandler) except Exception, error: from fwbackups import fwbackupsError raise fwbackupsError(_('Could not set up the logger: %s' % str(error)))
def __init__(self, name, level=logging.DEBUG): """Setup the fwbackups logger, text mode""" logging.Logger.__init__(self, name, level) self.__printToo = False self.__functions = [] self.__newmessages = False try: # need a handler loghandler = logging.FileHandler(encode(LOGLOC), 'a') # Create formatter & add formatter to handler logformatter = logging.Formatter("%(message)s") loghandler.setFormatter(logformatter) # add handler to logger self.addHandler(loghandler) except Exception, error: from fwbackups import fwbackupsError raise fwbackupsError( _('Could not set up the logger: %s' % str(error)))
def removeOldBackups(self): """Get list of old backups and remove them""" # get listing, local or remote if self.options['DestinationType'] == 'remote (ssh)': client, sftpClient = sftp.connect(self.options['RemoteHost'], self.options['RemoteUsername'], self.options['RemotePassword'], self.options['RemotePort']) listing = sftpClient.listdir(self.options['RemoteFolder']) else: listing = os.listdir(encode(self.options['Destination'])) # Normalize the unicode strings storing the filenames. This fixes a problem # on HFS+ filesystems where supplying a set name on the command line # resulted in a different Unicode string than the filename of the set. listing = [decode(i, filename=True) for i in listing] listing.sort() oldbackups = [] for i in listing: backupPrefix = '%s-%s-' % (_('Backup'), self.config.getSetName()) if i.startswith(backupPrefix): oldbackups.append(i) # ...And remove them. oldbackups.reverse() if self.options['DestinationType'] == 'remote (ssh)': for i in oldbackups[self.options['OldToKeep']:]: remoteBackup = os.path.join(self.options['RemoteFolder'], i) self.logger.logmsg('DEBUG', _('Removing old backup `%(a)s\' on %(b)s') % {'a': remoteBackup, 'b': self.options['RemoteHost']}) sftp.remove(sftpClient, remoteBackup) sftpClient.close() client.close() else: if self.options['Engine'] == 'rsync' and self.options['Incremental'] and oldbackups: for i in oldbackups[:-1]: self.logger.logmsg('DEBUG', _('Removing old backup `%s\'') % i) path = os.path.join(self.options['Destination'], i) shutil_modded.rmtree(encode(path), onerror=self.onError) oldIncrementalBackup = os.path.join(self.options['Destination'], oldbackups[-1]) if not oldIncrementalBackup.endswith('.tar') and not oldIncrementalBackup.endswith('.tar.gz') and \ not oldIncrementalBackup.endswith('.tar.bz2'): # oldIncrementalBackup = rsync self.logger.logmsg('DEBUG', _('Moving `%s\' to `%s\'') % (oldIncrementalBackup, self.dest)) shutil_modded.move(encode(oldIncrementalBackup), encode(self.dest)) else: # source = is not a rsync backup - remove it and start fresh self.logger.logmsg('DEBUG', _('`%s\' is not an rsync backup - removing.') % oldIncrementalBackup) shutil_modded.rmtree(encode(oldIncrementalBackup), onerror=self.onError) else: for i in oldbackups[self.options['OldToKeep']:]: self.logger.logmsg('DEBUG', _('Removing old backup `%s\'') % i) path = os.path.join(self.options['Destination'], i) shutil_modded.rmtree(encode(path), onerror=self.onError)
def tokens_replace(self, text, date, successful=None): """Replace tokens in the supplied text""" tokens = { "backup": os.path.basename(encode(self.dest)), "set": self.config.getSetName(), "date": date, "remote_host": self.options["RemoteHost"], "remote_username": self.options["RemoteUsername"], "remote_password": self.options["RemotePassword"], "remote_port": str(self.options["RemotePort"]), } # Only create the success token if we explicitly set to True/False if successful != None: if successful: tokens["successful"] = 1 else: tokens["successful"] = 0 # Adjust destination folder for remote backups if self.options["DestinationType"] == "remote (ssh)": tokens["destination"] = self.options["Destination"] else: tokens["destination"] = self.options["RemoteFolder"] def replace_match(m): """Replace non-escaped tokens with values at the beginning of a string""" return r"%s" % tokens[m.group(1)] def replace_match_escape(m): """Replace non-escaped tokens with values after the beginning of a string""" return r"%s%s" % (m.group(1), tokens[m.group(2)]) for token, sub in tokens.items(): text = re.sub(r"^\[(%s)\]" % token, replace_match, text) text = re.sub(r"([^\\]+?)\[(%s)\]" % token, replace_match_escape, text) # Replace escaped tokens into their non-escaped form text = text.replace(r"\[%s]" % token, "[%s]" % token) return text
def tokens_replace(self, text, date, successful=None): """Replace tokens in the supplied text""" tokens = {'backup': os.path.basename(encode(self.dest)), 'set': self.config.getSetName(), 'date': date, 'remote_host': self.options['RemoteHost'], 'remote_username': self.options['RemoteUsername'], 'remote_password': self.options['RemotePassword'], 'remote_port': str(self.options['RemotePort']), } # Only create the success token if we explicitly set to True/False if successful != None: if successful: tokens['successful'] = 1 else: tokens['successful'] = 0 # Adjust destination folder for remote backups if self.options['DestinationType'] == 'remote (ssh)': tokens['destination'] = self.options['RemoteFolder'] else: tokens['destination'] = self.options['Destination'] def replace_match(m): """Replace non-escaped tokens with values at the beginning of a string""" return r"%s" % tokens[m.group(1)] def replace_match_escape(m): """Replace non-escaped tokens with values after the beginning of a string""" return r"%s%s" % (m.group(1), tokens[m.group(2)]) for token, sub in tokens.items(): text = re.sub(r"^\[(%s)\]" % token, replace_match, text) text = re.sub(r"([^\\]+?)\[(%s)\]" % token, replace_match_escape, text) # Replace escaped tokens into their non-escaped form text = text.replace(r"\[%s]" % token, "[%s]" % token) return text
sys.exit(1) prefs = config.PrefsConf(create=True) if verbose == True or int(prefs.get('Preferences', 'AlwaysShowDebug')) == 1: level = fwlogger.L_DEBUG else: level = fwlogger.L_INFO logger = fwlogger.getLogger() logger.setLevel(level) logger.setPrintToo(printToo) # handle ctrl + c signal.signal(signal.SIGINT, handleStop) for setPath in sets: if MSWINDOWS: setPath = setPath.strip("'") setPath = os.path.join(SETLOC, "%s.conf" % setPath) if not os.path.exists(encode(setPath)): logger.logmsg("ERROR", _("The set configuration for '%s' was not found - skipping." % setPath)) continue try: backupHandle = backup.SetBackupOperation(setPath, logger=logger, forceRun=forceRun) backupThread = fwbackups.FuncAsThread(backupHandle.start, {}) except Exception, error: import traceback (etype, evalue, tb) = sys.exc_info() tracebackText = ''.join(traceback.format_exception(etype, evalue, tb)) logger.setPrintToo(True) logger.logmsg('WARNING', _("An error occurred while initializing the backup operation!")) logger.logmsg('ERROR', tracebackText) sys.exit(1) backupThread.start() try:
prefs = config.PrefsConf(create=True) if verbose == True or int(prefs.get('Preferences', 'AlwaysShowDebug')) == 1: level = fwlogger.L_DEBUG else: level = fwlogger.L_INFO logger = fwlogger.getLogger() logger.setLevel(level) logger.setPrintToo(printToo) # handle ctrl + c signal.signal(signal.SIGINT, handleStop) for setPath in sets: if MSWINDOWS: setPath = setPath.strip("'") setPath = os.path.join(SETLOC, "%s.conf" % setPath) if not os.path.exists(encode(setPath)): logger.logmsg( "ERROR", _("The set configuration for '%s' was not found - skipping." % setPath)) continue try: backupHandle = backup.SetBackupOperation(setPath, logger=logger, forceRun=forceRun) backupThread = fwbackups.FuncAsThread(backupHandle.start, {}) except Exception, error: import traceback (etype, evalue, tb) = sys.exc_info() tracebackText = ''.join( traceback.format_exception(etype, evalue, tb))
class RestoreOperation(operations.Common): def __init__(self, restorePath, logger=None): """Initializes a restore operation. If no logger is specified, a new one will be created.""" operations.Common.__init__(self, logger) self.logger.logmsg('INFO', _('Starting restore operation')) self.config = config.RestoreConf(restorePath) self.options = self.getOptions(self.config) self.options[ 'Engine'] = 'null' # workaround so prepareDestinationFolder doesn't complain def getOptions(self, conf): """Loads all the configuration options from a restore configuration file and returns them in a dictionary""" def _bool(value): return value in [1, '1', True, 'True'] options = conf.getOptions() if not options['RemotePort']: options['RemotePort'] = 22 else: options['RemotePort'] = int(options['RemotePort']) options['RemotePassword'] = options['RemotePassword'].decode('base64') return options def tarfile_generator(self, members): """Generator function for the tar extraction""" for tarinfo in members: self.ifCancel(members) self._currentName = os.path.basename(tarinfo.name) yield tarinfo def start(self): """Restores a backup""" wasErrors = False if self.options['RemoteSource']: # check if server settings are OK self.logger.logmsg('DEBUG', _('Attempting to connect to server')) thread = fwbackups.runFuncAsThread(sftp.testConnection, self.options['RemoteHost'], self.options['RemoteUsername'], self.options['RemotePassword'], self.options['RemotePort'], self.options['RemoteSource']) while thread.retval == None: time.sleep(0.1) # Check for errors, if any import paramiko import socket if thread.retval == True: pass elif type(thread.exception) == IOError: self.logger.logmsg('ERROR', _('The restore source was either not ' + \ 'found or it cannot be read due to insufficient permissions.')) return False elif type(thread.exception) == paramiko.AuthenticationException: self.logger.logmsg('ERROR', _('A connection was established, but authentication ' + \ 'failed. Please verify the username and password ' + \ 'and try again.')) return False elif type(thread.exception) == socket.gaierror or type( thread.exception) == socket.error: self.logger.logmsg('ERROR', _('A connection to the server could not be established.\n' + \ 'Error %(a)s: %(b)s' % {'a': type(thread.exception), 'b': str(thread.exception)} + \ '\nPlease verify your settings and try again.')) return False elif type(thread.exception) == socket.timeout: self.logger.logmsg('ERROR', _('A connection to the server has timed out. ' + \ 'Please verify your settings and try again.')) return False elif type(thread.exception) == paramiko.SSHException: self.logger.logmsg('ERROR', _('A connection to the server could not be established ' + \ 'because an error occurred: %s' % str(thread.exception) + \ '\nPlease verify your settings and try again.')) return False # source types: 'set' 'local archive' 'local folder' # 'remote archive (SSH)', 'remote folder (SSH)' # We don't want to raise a hard error, that's already in the log. # So we settle for a simple return false. # don't need source type logic, /destination/ is always a folder if not self.prepareDestinationFolder(self.options['Destination']): return False # Receive files from remote server if self.options['RemoteSource']: self.logger.logmsg('INFO', _('Receiving files from server')) self._status = STATUS_RECEIVING_FROM_REMOTE # receiving files try: # download file to location where we expect source to be client, sftpClient = sftp.connect( self.options['RemoteHost'], self.options['RemoteUsername'], self.options['RemotePassword'], self.options['RemotePort']) retval = sftp.receive(sftpClient, self.options['RemoteSource'], self.options['Destination']) # This is used later to terminate the restore operation early remoteSourceIsFolder = sftp.isFolder( sftpClient, self.options['RemoteSource']) sftpClient.close() client.close() except Exception, error: self.logger.logmsg( 'ERROR', _('Could not receive file from server: %s' % error)) wasErrors = True if wasErrors or remoteSourceIsFolder: # We are dealing with a remote folder - our job here is done # Either that or an error was encountered above self.logger.logmsg('INFO', _('Finished restore operation')) return not wasErrors self._status = STATUS_RESTORING # restoring try: if self.options['SourceType'] == 'set': # we don't know the type if os.path.isfile(encode(self.options['Source'])): fh = tarfile.open(self.options['Source'], 'r') fh.extractall(encode(self.options['Destination'])) fh.close() elif os.path.isdir(encode( self.options['Source'])): # we are dealing with rsync shutil_modded.copytree(encode(self.options['Source']), encode(self.options['Destination'])) else: # oops, something is up self.logger.logmsg( 'ERROR', _('Source `%s\' is not a file or folder!' % self.options['Source'])) return False elif self.options['SourceType'] == 'local archive' or self.options[ 'SourceType'] == 'remote archive (SSH)': if os.path.isfile(encode(self.options['Source'])): fh = tarfile.open(self.options['Source'], 'r') fh.extractall(encode(self.options['Destination'])) fh.close() else: # oops, something is up self.logger.logmsg( 'ERROR', _('Source `%s\' is not an archive!' % self.options['Source'])) return False else: # folders if not os.path.isdir(encode(self.options['Source'])): self.logger.logmsg( 'ERROR', _('Source `%s\' is not a folder' % self.options['Source'])) return False if not self.prepareDestinationFolder( self.options['Destination']): return False shutil_modded.copytree(encode(self.options['Source']), encode(self.options['Destination'])) # Clean up transfered files from remote server if self.options['SourceType'] == 'remote archive (SSH)' or ( self.options['SourceType'] == 'set' and self.options['RemoteSource']): os.remove(encode(self.options['Source'])) except: self.logger.logmsg( 'ERROR', 'Error(s) occurred while restoring certain files or folders.\nPlease check the traceback below to determine if any files are incomplete or missing.' ) import sys import traceback (etype, value, tb) = sys.exc_info() tracebackText = ''.join( traceback.format_exception(etype, value, tb)) self.logger.logmsg('ERROR', _('Traceback: %s' % tracebackText)) wasErrors = True # All done! self.logger.logmsg('INFO', _('Finished restore operation')) return not wasErrors
def start(self): """Start the backup process. Should be called after executing user command.""" if not self.options['Enabled']: # set is disabled return True self.logger.logmsg('INFO', _('Starting automatic backup operation of set `%s\'') % self.config.getSetName()) if self.options["CommandBefore"]: self._status = STATUS_EXECING_USER_COMMAND # Find tokens and substitute them tokenized_command = self.tokens_replace(self.options["CommandBefore"], self.date) self.execute_user_command(1, tokenized_command) try: # Get the list of paths... paths = self.parsePaths(self.config) if not paths: self.logger.logmsg('WARNING', _('There are no paths to backup!')) return False self.ifCancel() if self.options['DestinationType'] == 'remote (ssh)': # check if server settings are OK self.checkRemoteServer() self._status = STATUS_CLEANING_OLD if not (self.options['Engine'] == 'rsync' and self.options['Incremental']) and \ not self.options['DestinationType'] == 'remote (ssh)': if not self.prepareDestinationFolder(self.options['Destination']): return False if not (self.options['Engine'] == 'rsync' and self.options['Incremental']) \ and os.path.exists(encode(self.dest)): self.logger.logmsg('WARNING', _('`%s\' exists and will be overwritten.') % self.dest) shutil_modded.rmtree(encode(self.dest), onerror=self.onError) self.ifCancel() # Remove old stuff self.removeOldBackups() self.ifCancel() self._status = STATUS_INITIALIZING if self.options['PkgListsToFile']: pkgListfiles = self.createPkgLists() else: pkgListfiles = [] if self.options['DiskInfoToFile']: pkgListfiles.append(self.createDiskInfo()) self.ifCancel() command = self.parseCommand() self.addListFilesToBackup(pkgListfiles, command, self.options['Engine'], paths) # Now that the paths & commands are set up... retval = self.backupPaths(paths, command) self.deleteListFiles(pkgListfiles) if self.options['DestinationType'] == 'local': try: os.chmod(self.dest, 0711) except: pass # Exception handlers in FuncAsThread() must return retval same values except exceptions.SystemExit: # cancelled; the only time we skip the after command return -2 except: retval = False import traceback (etype, value, tb) = sys.exc_info() self.traceback = ''.join(traceback.format_exception(etype, value, tb)) self.logger.logmsg('WARNING', _('There was an error while performing the backup!')) self.logger.logmsg('ERROR', self.traceback) # just incase we have leftover stuff running self.cancelOperation() if self.options["CommandAfter"]: self._status = STATUS_EXECING_USER_COMMAND # Find tokens and substitute them tokenized_command = self.tokens_replace(self.options["CommandAfter"], self.date, retval) self.execute_user_command(2, tokenized_command) # All done! self.logger.logmsg('INFO', _("Finished automatic backup operation of set '%s'") % self.config.getSetName()) return retval
self.logger.logmsg('ERROR', _('Process exited with status %(a)s. Errors: %(b)s' % {'a': retval, 'b': ''.join(errors)})) elif self.options['Engine'] == 'rsync': # in this case, self.{folderdest,dest} both need to be created if self.options['DestinationType'] == 'remote (ssh)': client, sftpClient = sftp.connect(self.options['RemoteHost'], self.options['RemoteUsername'], self.options['RemotePassword'], self.options['RemotePort']) if not wasAnError: for i in paths: if self.toCancel: # Check if we need to cancel in between paths # If so, break and close the SFTP session # Immediately after, self.ifCancel() is run. break self._current += 1 self.logger.logmsg('DEBUG', _('Backing up path %(a)i/%(b)i: %(c)s') % {'a': self._current, 'b': self._total, 'c': i}) if not os.path.exists(encode(i)): self.logger.logmsg('WARNING', _("Path %s is missing or cannot be read and will be excluded from the backup.") % i) sftp.put(sftpClient, encode(i), encode(os.path.normpath(self.options['RemoteFolder']+os.sep+os.path.basename(self.dest)+os.sep+os.path.dirname(i))), symlinks=not self.options['FollowLinks'], excludes=encode(self.options['Excludes'].split('\n'))) sftpClient.close() client.close() else: # destination is local for i in paths: self.ifCancel() self._current += 1 if MSWINDOWS: # let's deal with real paths self.logger.logmsg('DEBUG', _('Backing up path %(a)i/%(b)i: %(c)s' % {'a': self._current, 'b': self._total, 'c': i})) shutil_modded.copytree_fullpaths(encode(i), encode(self.dest)) else: # not MSWINDOWS; UNIX/OS X can call rsync binary i = fwbackups.escapeQuotes(i, 1) self.logger.logmsg('DEBUG', _("Running command: nice -n %(a)i %(b)s %(c)s '%(d)s'" % {'a': self.options['Nice'], 'b': command, 'c': i, 'd': fwbackups.escapeQuotes(self.dest, 1)}))
self.options["RemotePassword"], self.options["RemotePort"], ) if not wasAnError: for i in paths: if self.toCancel: # Check if we need to cancel in between paths # If so, break and close the SFTP session # Immediately after, self.ifCancel() is run. break self._current += 1 self.logger.logmsg( "DEBUG", _("Backing up path %(a)i/%(b)i: %(c)s") % {"a": self._current, "b": self._total, "c": i}, ) if not os.path.exists(encode(i)): self.logger.logmsg( "WARNING", _("Path %s is missing or cannot be read and will be excluded from the backup.") % i, ) sftp.put( sftpClient, encode(i), encode( os.path.normpath( self.options["RemoteFolder"] + os.sep + os.path.basename(self.dest) + os.sep + os.path.dirname(i) )