# Copyright 2002 Ben Escoto # # This file is part of rdiff-backup. # # rdiff-backup is free software; you can redistribute it and/or modify # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # rdiff-backup 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 rdiff-backup; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA """list, delete, and otherwise manage increments""" from __future__ import generators from log import Log import Globals, Time, static, statistics, restore, selection, FilenameMapping class ManageException(Exception): pass def get_file_type(rp): """Returns one of "regular", "directory", "missing", or "special".""" if not rp.lstat(): return "missing" elif rp.isdir(): return "directory" elif rp.isreg(): return "regular" else: return "special" def get_inc_type(inc): """Return file type increment represents""" assert inc.isincfile() type = inc.getinctype() if type == "dir": return "directory" elif type == "diff": return "regular" elif type == "missing": return "missing" elif type == "snapshot": return get_file_type(inc) else: assert None, "Unknown type %s" % (type,) def describe_incs_parsable(incs, mirror_time, mirrorrp): """Return a string parsable by computer describing the increments Each line is a time in seconds of the increment, and then the type of the file. It will be sorted oldest to newest. For example: 10000 regular 20000 directory 30000 special 40000 missing 50000 regular <- last will be the current mirror """ incpairs = [(inc.getinctime(), inc) for inc in incs] incpairs.sort() result = ["%s %s" % (time, get_inc_type(inc)) for time, inc in incpairs] result.append("%s %s" % (mirror_time, get_file_type(mirrorrp))) return "\n".join(result) def describe_incs_human(incs, mirror_time, mirrorrp): """Return a string describing all the the root increments""" incpairs = [(inc.getinctime(), inc) for inc in incs] incpairs.sort() result = ["Found %d increments:" % len(incpairs)] if Globals.chars_to_quote: for time, inc in incpairs: result.append(" %s %s" % (FilenameMapping.unquote(inc.dirsplit()[1]), Time.timetopretty(time))) else: for time, inc in incpairs: result.append(" %s %s" % (inc.dirsplit()[1], Time.timetopretty(time))) result.append("Current mirror: %s" % Time.timetopretty(mirror_time)) return "\n".join(result) def delete_earlier_than(baserp, time): """Deleting increments older than time in directory baserp time is in seconds. It will then delete any empty directories in the tree. To process the entire backup area, the rdiff-backup-data directory should be the root of the tree. """ baserp.conn.manage.delete_earlier_than_local(baserp, time) def delete_earlier_than_local(baserp, time): """Like delete_earlier_than, but run on local connection for speed""" assert baserp.conn is Globals.local_connection def yield_files(rp): if rp.isdir(): for filename in rp.listdir(): for sub_rp in yield_files(rp.append(filename)): yield sub_rp yield rp for rp in yield_files(baserp): if ((rp.isincfile() and rp.getinctime() < time) or (rp.isdir() and not rp.listdir())): Log("Deleting increment file %s" % rp.path, 5) rp.delete() class IncObj: """Increment object - represent a completed increment""" def __init__(self, incrp): """IncObj initializer incrp is an RPath of a path like increments.TIMESTR.dir standing for the root of the increment. """ if not incrp.isincfile(): raise ManageException("%s is not an inc file" % incrp.path) self.incrp = incrp self.time = incrp.getinctime() def getbaserp(self): """Return rp of the incrp without extensions""" return self.incrp.getincbase() def pretty_time(self): """Return a formatted version of inc's time""" return Time.timetopretty(self.time) def full_description(self): """Return string describing increment""" s = ["Increment file %s" % self.incrp.path, "Date: %s" % self.pretty_time()] return "\n".join(s) def ListIncrementSizes(mirror_root, index): """Return string summarizing the size of all the increments""" stat_obj = statistics.StatsObj() # used for byte summary string def get_total(rp_iter): """Return the total size of everything in rp_iter""" total = 0 for rp in rp_iter: total += rp.getsize() return total def get_time_dict(inc_iter): """Return dictionary pairing times to total size of incs""" time_dict = {} for inc in inc_iter: if not inc.isincfile(): continue t = inc.getinctime() if not time_dict.has_key(t): time_dict[t] = 0 time_dict[t] += inc.getsize() return time_dict def get_mirror_select(): """Return iterator of mirror rpaths""" mirror_base = mirror_root.new_index(index) mirror_select = selection.Select(mirror_base) if not index: # must exclude rdiff-backup-directory mirror_select.parse_rbdir_exclude() return mirror_select.set_iter() def get_inc_select(): """Return iterator of increment rpaths""" inc_base = Globals.rbdir.append_path('increments', index) for base_inc in restore.get_inclist(inc_base): yield base_inc if inc_base.isdir(): inc_select = selection.Select(inc_base).set_iter() for inc in inc_select: yield inc def get_summary_triples(mirror_total, time_dict): """Return list of triples (time, size, cumulative size)""" triples = [] cur_mir_base = Globals.rbdir.append('current_mirror') mirror_time = restore.get_inclist(cur_mir_base)[0].getinctime() triples.append((mirror_time, mirror_total, mirror_total)) inc_times = time_dict.keys() inc_times.sort() inc_times.reverse() cumulative_size = mirror_total for inc_time in inc_times: size = time_dict[inc_time] cumulative_size += size triples.append((inc_time, size, cumulative_size)) return triples def triple_to_line(triple): """Convert triple to display string""" time, size, cum_size = triple return "%24s %13s %15s" % \ (Time.timetopretty(time), stat_obj.get_byte_summary_string(size), stat_obj.get_byte_summary_string(cum_size)) mirror_total = get_total(get_mirror_select()) time_dict = get_time_dict(get_inc_select()) triples = get_summary_triples(mirror_total, time_dict) l = ['%12s %9s %15s %20s' % ('Time', '', 'Size', 'Cumulative size'), '-' * 77, triple_to_line(triples[0]) + ' (current mirror)'] for triple in triples[1:]: l.append(triple_to_line(triple)) return '\n'.join(l)