def execute_user_command(self, cmd_type, command): """Run the after user command""" # Before or after command? if cmd_type == 1: cmd_type_str = 'Before' elif cmd_type == 2: cmd_type_str = 'After' else: raise ValueError('Unknown command type value "%s"' % cmd_type) # Execute the command self.logger.logmsg('INFO', _("Executing '%s' command") % cmd_type_str) sub = fwbackups.executeSub(command, env=self.environment, shell=True) self.pids.append(sub.pid) self.logger.logmsg('DEBUG', _('Starting subprocess with PID %s') % sub.pid) # track stdout errors = [] # use nonblocking I/O fl = fcntl.fcntl(sub.stderr, fcntl.F_GETFL) fcntl.fcntl(sub.stderr, fcntl.F_SETFL, fl | os.O_NONBLOCK) while sub.poll() in ["", None]: time.sleep(0.01) try: errors += sub.stderr.readline() except IOError, description: pass
def history(self): """Get + print all old log messages stored in the log file""" stream = open(LOGLOC, 'r') text = stream.read() stream.close() if text.strip() != '': self.console.write_line('-- %s --%s%s' % (_('Previous log messages')\ , os.linesep, os.linesep), self.console.style_ps2) for i in text.split(os.linesep): errType = i.split(' ') if len(errType) >= 5: if errType[4] == _('CRITICAL') or errType[4] == _('ERROR'): self.console.write_line('%s%s' % (i, os.linesep), self.console.style_err) elif errType[4] == _('WARNING'): self.console.write_line('%s%s' % (i, os.linesep), self.console.style_warn) else: self.console.write_line('%s%s' % (i, os.linesep), self.console.style_ps1) else: self.console.write_line('%s%s' % (i, os.linesep), self.console.style_ps1) else: self.console.write_line('-- %s --%s' % (_('No previous log messages to display'),\ os.linesep), self.console.style_ps2)
def __init__(self, treeview, statusbar, ui): self.treeview = treeview self.logger = fwlogger.getLogger() self.statusbar = statusbar self.ui = ui self.liststore = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING) # Give it columns cell = gtk.CellRendererToggle() cell.connect('toggled', self._on_toggled, self.liststore) cell.set_property('activatable', True) col = gtk.TreeViewColumn(_('Export'), cell, active=0) col.set_resizable(True) self.treeview.append_column(col) cell = gtk.CellRendererText() cell.set_property('ellipsize', pango.ELLIPSIZE_MIDDLE) col = gtk.TreeViewColumn(_('Set'), cell, text=1) col.set_resizable(True) self.treeview.append_column(col) self.treeview.set_model(self.liststore) self.treeview.set_reorderable(False) # Just to keep things clean. self._clear()
def _load(self): """Load all set .conf files into the view""" loaded_count = 0 self.logger.logmsg('DEBUG', _('Parsing configuration files')) files = os.listdir(SETLOC) for file in files: if file.endswith('.conf'): self.liststore.append([True, file.split('.conf')[0]]) else: self.logger.logmsg('WARNING', _('Refusing to parse file `%s\': configuration files must end in `.conf\'') % file)
def cancelOperation(self): """Requests the current operation stops as soon as possible""" self.logger.logmsg('INFO', _('Canceling the current operation!')) if self.pids: for process in self.pids: try: self.logger.logmsg('DEBUG', _('Stopping process with ID %s') % process) fwbackups.kill(process, 9) except Exception, error: self.logger.logmsg('WARNING', _('Could not stop process %(a)s: %(b)s' % (process, error))) continue
def write_log_line(self, i): """Wrapper for write_line for log messages""" errType = i.split(' ') if len(errType) >= 5: if errType[4] == _('CRITICAL') or errType[4] == _('ERROR'): self.write_line('%s%s' % (i, os.linesep), self.style_err) elif errType[4] == _('WARNING'): self.write_line('%s%s' % (i, os.linesep), self.style_warn) else: self.write_line('%s%s' % (i, os.linesep), self.style_ps1) else: self.write_line('%s%s' % (i, os.linesep), self.style_ps1)
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 _load(self): """Load all set .conf files into the view""" loaded_count = 0 self.logger.logmsg('DEBUG', _('Parsing configuration files')) files = os.listdir(SETLOC) for file in files: if file.endswith('.conf'): self.liststore.append([True, file.split('.conf')[0]]) else: self.logger.logmsg( 'WARNING', _('Refusing to parse file `%s\': configuration files must end in `.conf\'' ) % file)
def history(self): """Get + print all old log messages stored in the log file""" stream = open(LOGLOC, 'r') text = stream.read() stream.close() if text.strip() != '': self.write_line('-- %s --%s%s' % (_('Previous log messages'), os.linesep, os.linesep), self.style_ps2) for i in text.split('\n'): self.write_log_line(i) else: self.write_line('-- %s --%s' % (_('No previous log messages to display'),\ os.linesep), self.style_ps2) self.write_line('-- %s --%s%s' % (_('Current session\'s log messages'), os.linesep, os.linesep), self.style_ps2)
def createDiskInfo(self): """Print disk info to a file in tempdir""" fd, path = tempfile.mkstemp(suffix='.txt', prefix="%s - tmp" % _('Disk Information')) fh = os.fdopen(fd, 'w') retval, stdout, stderr = fwbackups.execute('fdisk -l', env=self.environment, shell=True, stdoutfd=fh) fh.close() return path
def __init__(self, console): logging.FileHandler.__init__(self, LOGLOC) self.console = console self.history() self.stream = None self.console.write_line('-- %s --%s%s' % (_('Current session\'s log \ messages'), os.linesep, os.linesep), self.console.style_ps2)
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 __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 __init__(self, console): logging.FileHandler.__init__(self, LOGLOC) self.console = console self.history() self.stream = None self.console.write_line( '-- %s --%s%s' % (_('Current session\'s log \ messages'), os.linesep, os.linesep), self.console.style_ps2)
def addFolder(self): """Add a folder to the pathview""" fileDialog = PathDia(self.ui.path_dia, _('Choose folder(s)'), self.parent, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, multiple=True) response = fileDialog.run() if response == gtk.RESPONSE_OK: paths = [path.decode('utf-8') for path in fileDialog.get_filenames()] self.add(paths, self._buildListstoreIndex(self.liststore, 1)) fileDialog.destroy()
def ifCancel(self, fh=None): """Checks if another thread requested the operation be cancelled via cancelOperation(). If a file-handle 'fh' is given, it will be closed.""" if self.toCancel: self.logger.logmsg('INFO', _('The operation has been cancelled.')) if fh != None: fh.close() raise SystemExit
def __init__(self, onetPath, logger=None): """Initializes a one-time backup operation. If no logger is specified, a new one will be created. logger will be created if needed.""" BackupOperation.__init__(self, logger) self.logger.logmsg("INFO", _("Starting one-time backup operation")) self.config = config.OneTimeConf(onetPath) self.options = self.getOptions(self.config) # Parse backup folder format date = time.strftime("%Y-%m-%d_%H-%M") self.dest = os.path.join(self.options["Destination"], "%s-%s-%s" % (_("Backup"), _("OneTime"), date)) # IF tar || tar.gz, add .tar || tar.gz respectively to the dest since # the dest is to be a file, not a folder... if self.options["Engine"] == "tar": self.dest += ".tar" elif self.options["Engine"] == "tar.gz": self.dest += ".tar.gz" elif self.options["Engine"] == "tar.bz2": self.dest += ".tar.bz2"
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 __init__(self, onetPath, logger=None): """Initializes a one-time backup operation. If no logger is specified, a new one will be created. logger will be created if needed.""" BackupOperation.__init__(self, logger) self.logger.logmsg('INFO', _('Starting one-time backup operation')) self.config = config.OneTimeConf(onetPath) self.options = self.getOptions(self.config) # Parse backup folder format date = time.strftime('%Y-%m-%d_%H-%M') self.dest = os.path.join(self.options['Destination'], "%s-%s-%s" % (_('Backup'), _('OneTime'), date)) # IF tar || tar.gz, add .tar || tar.gz respectively to the dest since # the dest is to be a file, not a folder... if self.options['Engine'] == 'tar': self.dest += '.tar' elif self.options['Engine'] == 'tar.gz': self.dest += '.tar.gz' elif self.options['Engine'] == 'tar.bz2': self.dest += '.tar.bz2'
def putFolder(sftp, src, dst, symlinks=False, excludes=[]): """Copies src (local) to dst/[src] (remote). Folder dst must exist""" # Check if the src itself is an exclude # FIXME: This means we check any subdirectory against excludes 2x SKIPME = 0 for exclude in excludes: # excludes pattern matching if src in myglob(exclude, os.path.dirname(src)): SKIPME = 1 break # it is pointless to process further excludes if SKIPME: return errors = [] dst = os.path.join(dst, os.path.basename(src)) if not exists(sftp, dst): sftp.mkdir(dst) #sftp.chdir(dst) for item in os.listdir(src): # make absolute paths src_abs = os.path.join(src, item) dst_abs = os.path.join(dst, item) SKIPME = 0 for exclude in excludes: # excludes pattern matching if src_abs in myglob(exclude, os.path.dirname(src_abs)): SKIPME = 1 break # it is pointless to process further excludes if SKIPME: continue try: # at this point, excludes have been handled and we're ready to copy. if symlinks and os.path.islink(src_abs): linkto = os.readlink(src_abs) sftp.symlink(linkto, dst_abs) elif os.path.isdir(src_abs): # run this again in the subfolder, uploading to the parent folder putFolder(sftp, src_abs, os.path.dirname(dst_abs), symlinks, excludes) elif os.path.isfile(src_abs): sftp.put(src_abs, dst_abs) else: print _( '`%s\' is not a file, folder or link! Skipping.') % src_abs except (IOError, os.error), reason: # don't halt the entire copy, just print the errors at the end errors.append('%s --> %s: %s' % (src_abs, dst_abs, reason))
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 removePath(self): """Remote a path from the pathview""" try: model, paths = self.treeview.get_selection().get_selected_rows() for i in paths: model, paths = self.treeview.get_selection().get_selected_rows() model.remove(model.get_iter(paths[0])) except TypeError: self.statusbar.newmessage(_('Please select a path before choosing an action.'), 3) return
def history(self): """Get + print all old log messages stored in the log file""" stream = open(LOGLOC, 'r') text = stream.read() stream.close() if text.strip() != '': self.write_line( '-- %s --%s%s' % (_('Previous log messages'), os.linesep, os.linesep), self.style_ps2) for i in text.split('\n'): self.write_log_line(i) else: self.write_line('-- %s --%s' % (_('No previous log messages to display'),\ os.linesep), self.style_ps2) self.write_line( '-- %s --%s%s' % (_('Current session\'s log messages'), os.linesep, os.linesep), self.style_ps2)
def putFolder(sftp, src, dst, symlinks=False, excludes=[]): """Copies src (local) to dst/[src] (remote). Folder dst must exist""" # Check if the src itself is an exclude # FIXME: This means we check any subdirectory against excludes 2x SKIPME = 0 for exclude in excludes: # excludes pattern matching if src in myglob(exclude, os.path.dirname(src)): SKIPME = 1 break # it is pointless to process further excludes if SKIPME: return errors = [] dst = os.path.join(dst, os.path.basename(src)) if not exists(sftp, dst): sftp.mkdir(dst) #sftp.chdir(dst) for item in os.listdir(src): # make absolute paths src_abs = os.path.join(src, item) dst_abs = os.path.join(dst, item) SKIPME = 0 for exclude in excludes: # excludes pattern matching if src_abs in myglob(exclude, os.path.dirname(src_abs)): SKIPME = 1 break # it is pointless to process further excludes if SKIPME: continue try: # at this point, excludes have been handled and we're ready to copy. if symlinks and os.path.islink(src_abs): linkto = os.readlink(src_abs) sftp.symlink(linkto, dst_abs) elif os.path.isdir(src_abs): # run this again in the subfolder, uploading to the parent folder putFolder(sftp, src_abs, os.path.dirname(dst_abs), symlinks, excludes) elif os.path.isfile(src_abs): sftp.put(src_abs, dst_abs) else: print _('`%s\' is not a file, folder or link! Skipping.') % src_abs except (IOError, os.error), reason: # don't halt the entire copy, just print the errors at the end errors.append('%s --> %s: %s' % (src_abs, dst_abs, reason))
def __init__(self, setPath, logger=None): """Initializes the automatic backup operation of set defined at setPath. If no logger is supplied, a new one will be created.""" BackupOperation.__init__(self, logger) self.config = config.BackupSetConf(setPath) self.options = self.getOptions(self.config) if self.options['Enabled']: self.logger.logmsg('INFO', _('Starting automatic backup operation of set `%s\'') % self.config.getSetName()) # Parse backup folder format # date stored as class variable due to re-use in user commands later self.date = time.strftime('%Y-%m-%d_%H-%M') self.dest = os.path.join(self.options['Destination'], u"%s-%s-%s" % (_('Backup'), self.config.getSetName(), self.date)) # set-specific options # IF tar || tar.gz, add .tar || tar.gz respectively to the dest since # the dest is to be a file, not a folder... if self.options['Engine'] == 'tar': self.dest += '.tar' elif self.options['Engine'] == 'tar.gz': self.dest += '.tar.gz' elif self.options['Engine'] == 'tar.bz2': self.dest += '.tar.bz2'
def removePath(self): """Remote a path from the pathview""" try: model, paths = self.treeview.get_selection().get_selected_rows() for i in paths: model, paths = self.treeview.get_selection().get_selected_rows( ) model.remove(model.get_iter(paths[0])) except TypeError: self.statusbar.newmessage( _('Please select a path before choosing an action.'), 3) return
def createPkgLists(self): """Create the pkg lists in tempdir""" # Start as a dictionary so we can keep track of which items we have already # processed. See comment at retur for more info. pkgListFiles = {} for path in os.environ["PATH"].split(":"): if os.path.exists(os.path.join(path, "rpm")) and not pkgListFiles.has_key("rpm"): fd, path = tempfile.mkstemp(suffix=".txt", prefix="%s - tmp" % _("rpm - Package list")) fh = os.fdopen(fd, "w") # try with rpm-python, but if not just execute it like the rest try: import rpm pyrpm = True except ImportError: pyrpm = False if pyrpm: ts = rpm.ts() # Equivalent to rpm -qa mi = ts.dbMatch() for hdr in mi: fh.write("%s-%s-%s.%s\n" % (hdr["name"], hdr["version"], hdr["release"], hdr["arch"])) else: retval, stdout, stderr = fwbackups.execute("rpm -qa", env=self.environment, shell=True, stdoutfd=fh) pkgListFiles["rpm"] = path fh.close() if os.path.exists(os.path.join(path, "pacman")) and not pkgListFiles.has_key("pacman"): fd, path = tempfile.mkstemp(suffix=".txt", prefix="%s - tmp" % _("Pacman - Package list")) fh = os.fdopen(fd, "w") retval, stdout, stderr = fwbackups.execute("pacman -Qq", env=self.environment, shell=True, stdoutfd=fh) pkgListFiles["pacman"] = path fh.close() if os.path.exists(os.path.join(path, "dpkg")) and not pkgListFiles.has_key("dpkg"): fd, path = tempfile.mkstemp(suffix=".txt", prefix="%s - tmp" % _("dpkg - Package list")) fh = os.fdopen(fd, "w") retval, stdout, stderr = fwbackups.execute("dpkg -l", env=self.environment, shell=True, stdoutfd=fh) pkgListFiles["dpkg"] = path fh.close() # We want to return a list of only the filenames return pkgListFiles.values()
def usage(error=None): if error: print _('Error: %s' % error) print _('Run with --help for usage information.') else: print _("""Usage: fwbackups-runonce --destination-type=TYPE --engine=ENGINE [OPTIONS] Path(s) Destination Options: -v, --verbose : Increase verbosity (print debug messages) -h, --help : Print this message and exit -r, --recursive : Perform a recursive backup -i, --hidden : Include hidden files in the backup -p, --packages2file : Print installed package list to a text file -d, --diskinfo2file : Print disk geometry to a file (must be root) -s, --sparse : Handle sparse files efficiently -e, --engine=ENGINE : Use specified backup engine. Valid engines are tar, tar.gz, tar.bz2 and rsync. -x, --exclude='PATTERN' : Skip files matching PATTERN. -n, --nice=NICE : Indicate the niceness of the backup process (-20 to 19) --destination-type=TYPE : Destination type (`local' or `remote (ssh)') --remote-host=HOSTNAME : Connect to remote host `HOSTNAME' --remote-user=USERNAME : Connect as specified username --remote-password=PASSWORD : Connect using password `PASSWORD' --remote-port=PORT : Connect on specified port (default 22) Path(s) is space-seperated list of paths to include in the backup. Destination is the directory where the OneTime backup should be placed within. Defaults to tar engine and local destination unless specified otherwise. Supplying --remote* will have no effect unless thedestination type is `remote (ssh)'.""")
def usage(error=None): if error: print _('Error: %s' % error) print _('Run with --help for usage information.') else: print _( """Usage: fwbackups-runonce --destination-type=TYPE --engine=ENGINE [OPTIONS] Path(s) Destination Options: -v, --verbose : Increase verbosity (print debug messages) -h, --help : Print this message and exit -r, --recursive : Perform a recursive backup -i, --hidden : Include hidden files in the backup -p, --packages2file : Print installed package list to a text file -d, --diskinfo2file : Print disk geometry to a file (must be root) -s, --sparse : Handle sparse files efficiently -e, --engine=ENGINE : Use specified backup engine. Valid engines are tar, tar.gz, tar.bz2 and rsync. -x, --exclude='PATTERN' : Skip files matching PATTERN. -n, --nice=NICE : Indicate the niceness of the backup process (-20 to 19) --destination-type=TYPE : Destination type (`local' or `remote (ssh)') --remote-host=HOSTNAME : Connect to remote host `HOSTNAME' --remote-user=USERNAME : Connect as specified username --remote-password=PASSWORD : Connect using password `PASSWORD' --remote-port=PORT : Connect on specified port (default 22) Path(s) is space-seperated list of paths to include in the backup. Destination is the directory where the OneTime backup should be placed within. Defaults to tar engine and local destination unless specified otherwise. Supplying --remote* will have no effect unless thedestination type is `remote (ssh)'.""")
def copytree(src, dst, symlinks=False): """Recursively copy a directory tree using copy2(). The destination directory must not already exist. If exception(s) occur, an Error is raised with a list of reasons. If the optional symlinks flag is true, symbolic links in the source tree result in symbolic links in the destination tree; if it is false, the contents of the files pointed to by symbolic links are copied.""" if os.path.isdir(src): names = os.listdir(src) else: names = [src] if not os.path.exists(dst): os.mkdir(dst, 0755) errors = [] for name in names: srcname = os.path.join(src, name) #dstname = os.path.join(dst, name) dstname = os.path.join(dst, os.path.split(name)[1]) try: if symlinks and os.path.islink(srcname): linkto = os.readlink(srcname) os.symlink(linkto, dstname) elif os.path.isdir(srcname): copytree(srcname, dstname, symlinks) elif not os.path.isfile(srcname): print _( '`%s\' isn\'t a file, folder or link! Skipping.') % srcname else: copy2(srcname, dstname) # XXX What about devices, sockets etc.? Done. except (IOError, os.error), why: errors.append('%s --> %s: %s' % (srcname, dstname, why)) # catch the Error from the recursive copytree so that we can # continue with other files except Error, err: errors.extend(err.args[0])
def createPkgLists(self): """Create the pkg lists in tempdir""" # Start as a dictionary so we can keep track of which items we have already # processed. See comment at retur for more info. pkgListFiles = {} for path in os.environ['PATH'].split(':'): if os.path.exists(os.path.join(path, 'rpm')) and not pkgListFiles.has_key('rpm'): fd, path = tempfile.mkstemp(suffix='.txt', prefix="%s - tmp" % _('rpm - Package list')) fh = os.fdopen(fd, "w") # try with rpm-python, but if not just execute it like the rest try: import rpm pyrpm = True except ImportError: pyrpm = False if pyrpm: ts=rpm.ts() # Equivalent to rpm -qa mi=ts.dbMatch() for hdr in mi: fh.write("%s-%s-%s.%s\n" % (hdr['name'], hdr['version'], hdr['release'], hdr['arch'])) else: retval, stdout, stderr = fwbackups.execute('rpm -qa', env=self.environment, shell=True, stdoutfd=fh) pkgListFiles['rpm'] = path fh.close() if os.path.exists(os.path.join(path, 'pacman')) and not pkgListFiles.has_key('pacman'): fd, path = tempfile.mkstemp(suffix='.txt', prefix="%s - tmp" % _('Pacman - Package list')) fh = os.fdopen(fd, 'w') retval, stdout, stderr = fwbackups.execute('pacman -Qq', env=self.environment, shell=True, stdoutfd=fh) pkgListFiles['pacman'] = path fh.close() if os.path.exists(os.path.join(path, 'dpkg')) and not pkgListFiles.has_key('dpkg'): fd, path = tempfile.mkstemp(suffix='.txt', prefix="%s - tmp" % _('dpkg - Package list')) fh = os.fdopen(fd, 'w') retval, stdout, stderr = fwbackups.execute('dpkg -l', env=self.environment, shell=True, stdoutfd=fh) pkgListFiles['dpkg'] = path fh.close() # We want to return a list of only the filenames return pkgListFiles.values()
def logmsg(self, severity, message): """Logs a message. Severity is one of 'debug', 'info', 'warning', 'error' or 'critical'.""" date = datetime.datetime.now().strftime('%b %d %H:%M:%S') level = self.getEffectiveLevel() if level <= LEVELS[severity]: entry = '%s :: %s : %s' % (date, _(severity), message) self.log(LEVELS[severity], entry) if self.__printToo: print entry.encode('utf-8') # pull in & execute the appropriate function for i in self.__functions: i(severity, entry)
def copytree(src, dst, symlinks=False): """Recursively copy a directory tree using copy2(). The destination directory must not already exist. If exception(s) occur, an Error is raised with a list of reasons. If the optional symlinks flag is true, symbolic links in the source tree result in symbolic links in the destination tree; if it is false, the contents of the files pointed to by symbolic links are copied.""" if os.path.isdir(src): names = os.listdir(src) else: names = [src] if not os.path.exists(dst): os.mkdir(dst, 0755) errors = [] for name in names: srcname = os.path.join(src, name) #dstname = os.path.join(dst, name) dstname = os.path.join(dst, os.path.split(name)[1]) try: if symlinks and os.path.islink(srcname): linkto = os.readlink(srcname) os.symlink(linkto, dstname) elif os.path.isdir(srcname): copytree(srcname, dstname, symlinks) elif not os.path.isfile(srcname): print _('`%s\' isn\'t a file, folder or link! Skipping.') % srcname else: copy2(srcname, dstname) # XXX What about devices, sockets etc.? Done. except (IOError, os.error), why: errors.append('%s --> %s: %s' % (srcname, dstname, why)) # catch the Error from the recursive copytree so that we can # continue with other files except Error, err: errors.extend(err.args[0])
def logmsg(self, severity, message): """Logs a message. Severity is one of 'debug', 'info', 'warning', 'error' or 'critical'.""" date = datetime.datetime.now().strftime('%b %d %H:%M:%S') level = self.getEffectiveLevel() if level <= LEVELS[severity]: entry = u'%s :: %s : %s' % (date.decode('utf-8'), _(severity), message) self.log(LEVELS[severity], entry.encode('utf-8')) if self.__printToo: print entry.encode('utf-8') # pull in & execute the appropriate function for i in self.__functions: i(severity, entry)
def addFolder(self): """Add a folder to the pathview""" fileDialog = PathDia(self.ui.path_dia, _('Choose folder(s)'), self.parent, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, multiple=True) response = fileDialog.run() if response == gtk.RESPONSE_OK: paths = [ path.decode('utf-8') for path in fileDialog.get_filenames() ] self.add(paths, self._buildListstoreIndex(self.liststore, 1)) fileDialog.destroy()
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, treeview, statusbar, ui): self.treeview = treeview self.logger = fwlogger.getLogger() self.statusbar= statusbar self.ui = ui self.liststore = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING) # Give it columns cell = gtk.CellRendererToggle() cell.connect('toggled', self._on_toggled, self.liststore) cell.set_property('activatable', True) col = gtk.TreeViewColumn(_('Export'), cell, active=0) col.set_resizable(True) self.treeview.append_column(col) cell = gtk.CellRendererText() cell.set_property('ellipsize', pango.ELLIPSIZE_MIDDLE) col = gtk.TreeViewColumn(_('Set'), cell, text=1) col.set_resizable(True) self.treeview.append_column(col) self.treeview.set_model(self.liststore) self.treeview.set_reorderable(False) # Just to keep things clean. self._clear()
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 usage(error=None): if error: print _('Error: %s' % error) print _('Run with --help for usage information.') else: print _("""Usage: fwbackups-run [OPTIONS] Set_Name(s) Options: -v, --verbose : Increase verbosity (print debug messages) -h, --help : Print this message and exit -l, --silent : Print messages to log file only Set_Name(s) is space-seperated list of set names to run backups of.""")
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 usage(error=None): if error: print _('Error: %s' % error) print _('Run with --help for usage information.') else: print _("""Usage: fwbackups-run [OPTIONS] Set_Name(s) Options: -v, --verbose : Increase verbosity (print debug messages) -h, --help : Print this message and exit -l, --silent : Print messages to log file only -f, --force : Run the backup even if the set is disabled. Set_Name(s) is space-seperated list of set names to run backups of.""")
def __init__(self, setPath, logger=None, forceRun=False): """Initializes the automatic backup operation of set defined at setPath. If no logger is supplied, a new one will be created.""" BackupOperation.__init__(self, logger) self.config = config.BackupSetConf(setPath) self.options = self.getOptions(self.config, forceEnabled=forceRun) # Parse backup folder format # date stored as class variable due to re-use in user commands later self.date = time.strftime('%Y-%m-%d_%H-%M') self.dest = os.path.join(self.options['Destination'], u"%s-%s-%s" % (_('Backup'), self.config.getSetName(), self.date)) # set-specific options # IF tar || tar.gz, add .tar || tar.gz respectively to the dest since # the dest is to be a file, not a folder... if self.options['Engine'] == 'tar': self.dest += '.tar' elif self.options['Engine'] == 'tar.gz': self.dest += '.tar.gz' elif self.options['Engine'] == 'tar.bz2': self.dest += '.tar.bz2'
def backupPaths(self, paths, command): """Does the actual copying dirty work""" # this is in common self._current = 0 if len(paths) == 0: return True self._total = len(paths) self._status = STATUS_BACKING_UP wasAnError = False if self.options['Engine'] == 'tar': if MSWINDOWS: self.logger.logmsg('INFO', _('Using %s on Windows: Cancel function will only take effect after a path has been completed.' % self.options['Engine'])) import tarfile fh = tarfile.open(self.dest, 'w') for i in paths: self.ifCancel() self._current += 1 self.logger.logmsg('DEBUG', _('Backing up path %(a)i/%(b)i: %(c)s' % {'a': self._current, 'b': self._total, 'c': i})) fh.add(i, recursive=self.options['Recursive']) fh.close() else: # not MSWINDOWS for i in paths: i = fwbackups.escapeQuotes(i, 1) self.ifCancel() self._current += 1 self.logger.logmsg('DEBUG', _("Running command: nice -n %(a)i %(b)s '%(c)s'" % {'a': self.options['Nice'], 'b': command, 'c': i})) sub = fwbackups.executeSub("nice -n %i %s '%s'" % (self.options['Nice'], command, i), env=self.environment, shell=True) self.pids.append(sub.pid) self.logger.logmsg('DEBUG', _('Starting subprocess with PID %s') % sub.pid) # track stdout errors = [] # use nonblocking I/O fl = fcntl.fcntl(sub.stderr, fcntl.F_GETFL) fcntl.fcntl(sub.stderr, fcntl.F_SETFL, fl | os.O_NONBLOCK) while sub.poll() in ["", None]: time.sleep(0.01) try: errors += sub.stderr.readline() except IOError, description: pass self.pids.remove(sub.pid) retval = sub.poll() self.logger.logmsg('DEBUG', _('Subprocess with PID %(a)s exited with status %(b)s' % {'a': sub.pid, 'b': retval})) # Something wrong? if retval != EXIT_STATUS_OK and retval != 2: wasAnError = True self.logger.logmsg('ERROR', 'An error occurred while backing up path \'%s\'.\nPlease check the error output below to determine if any files are incomplete or missing.' % str(i)) self.logger.logmsg('ERROR', _('Process exited with status %(a)s. Errors: %(b)s' % {'a': retval, 'b': ''.join(errors)}))
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 checkRemoteServer(self): """Checks if a connection to the remote server can be established""" self.logger.logmsg('DEBUG', _('Attempting to connect to server %s...') % self.options['RemoteHost']) thread = fwbackups.runFuncAsThread(sftp.testConnection, self.options['RemoteHost'], self.options['RemoteUsername'], self.options['RemotePassword'], self.options['RemotePort'], self.options['RemoteFolder']) while thread.retval == None: time.sleep(0.1) # Check for errors, if any import paramiko import socket if thread.retval == True: self.logger.logmsg('DEBUG', _('Attempt to connect succeeded.')) return True elif type(thread.exception) == IOError: self.logger.logmsg('ERROR', _('The backup destination was either not ' + \ 'found or it cannot be written to 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 else: # not remote, just pass return True
linkto = os.readlink(src_abs) sftp.symlink(linkto, dst_abs) elif os.path.isdir(src_abs): # run this again in the subfolder, uploading to the parent folder putFolder(sftp, src_abs, os.path.dirname(dst_abs), symlinks, excludes) elif os.path.isfile(src_abs): sftp.put(src_abs, dst_abs) else: print _( '`%s\' is not a file, folder or link! Skipping.') % src_abs except (IOError, os.error), reason: # don't halt the entire copy, just print the errors at the end errors.append('%s --> %s: %s' % (src_abs, dst_abs, reason)) if errors: print _('Could not copy some files due to errors:') print '\n'.join(errors) # Remember we wrap this in callbacks.py def testConnection(host, username, password, port, path): """Tests connecting to a SSH/SFTP connection with the supplied arguments. Returns True if connection was successful.""" client, sftp = connect(host, username, password, port, timeout=30) try: doesExist = isFolder(sftp, path) except IOError: # Not using finally: to remain compatible with python < 2.5 sftp.close() client.close() raise client.close()
def __init__(self, dialog, textview, parent, tracebackText): """Uses `dialog' to show bug report `traceback' in `textview' on top of `parent'""" GenericDia.__init__(self, dialog, _('Bug Report'), parent) textview.get_buffer().set_text(tracebackText)
def __init__(self, treeview, statusbar, ui, parent): self.treeview = treeview self.statusbar = statusbar self.ui = ui self.parent = parent self.logger = fwlogger.getLogger() # Give it columns cell = gtk.CellRendererPixbuf() col = gtk.TreeViewColumn(_('Access'), cell, stock_id=0) col.set_resizable(True) self.treeview.append_column(col) cell = gtk.CellRendererText() cell.set_property('ellipsize', pango.ELLIPSIZE_MIDDLE) col = gtk.TreeViewColumn(_('Path'), cell, text=1) col.set_resizable(True) self.treeview.append_column(col) self.liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) self.treeview.set_model(self.liststore) self.treeview.set_reorderable(False) self.treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) # Allow enable drag and drop of rows including row move #self.TARGETS = [('text/plain', 0, 1)] #self.treeview.enable_model_drag_dest(self.TARGETS, gtk.gdk.ACTION_DEFAULT) target = [('text/uri-list', 0, 0)] self.treeview.drag_dest_set(gtk.DEST_DEFAULT_ALL, target, gtk.gdk.ACTION_COPY) def escape(uri): "Convert each space to %20, etc" _to_utf8 = codecs.getencoder('utf-8') return re.sub('[^:-_./a-zA-Z0-9]', lambda match: '%%%02x' % ord(match.group(0)), _to_utf8(uri)[0]) def unescape(uri): "Convert each %20 to a space, etc" if '%' not in uri: return uri return re.sub('%[0-9a-fA-F][0-9a-fA-F]', lambda match: chr(int(match.group(0)[1:], 16)), uri) def get_local_path(uri): """Convert 'uri' to a local path and return, if possible. If 'uri' is a resource on a remote machine, return None. URI is in the escaped form (%20 for space).""" if not uri: return None if uri[0] == '/': if uri[1:2] != '/': return unescape(uri) # A normal Unix pathname i = uri.find('/', 2) if i == -1: return None # //something if i == 2: if MSWINDOWS: return unescape( uri[3:]) # ///path - from the removal of file:/// else: return unescape( uri[2:] ) # ///path - from the removal of file:// as we need / (is root) remote_host = uri[2:i] if remote_host == our_host_name(): return unescape(uri[i:]) # //localhost/path # //otherhost/path elif uri[:5].lower() == 'file:': if uri[5:6] == '/': return get_local_path(uri[5:]) elif uri[:2] == './' or uri[:3] == '../': return unescape(uri) return None def drag_data_received(w, context, x, y, data, info, time): paths = [] if data and data.format == 8: for i in data.data.split('\r\n'): if i != "" or i != None: path = get_local_path(i) if path != None: paths.append(path.decode('utf-8')) for i in paths: self.add([os.path.normpath(i)], self._buildListstoreIndex(self.liststore, 1)) context.finish(True, False, time) self.treeview.connect('drag_data_received', drag_data_received) # Just to keep things clean. self.clear()