From b53bfd4d41252426cb050ef896676034d92e3ef7 Mon Sep 17 00:00:00 2001 From: bescoto Date: Tue, 31 Dec 2002 08:46:22 +0000 Subject: Various changes for v0.11.1 (see CHANGELOG) git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup/trunk@256 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109 --- rdiff-backup/rdiff_backup/restore.py | 660 +++++++++++++++++++---------------- 1 file changed, 367 insertions(+), 293 deletions(-) (limited to 'rdiff-backup/rdiff_backup/restore.py') diff --git a/rdiff-backup/rdiff_backup/restore.py b/rdiff-backup/rdiff_backup/restore.py index c3a2f6c..ff09eee 100644 --- a/rdiff-backup/rdiff_backup/restore.py +++ b/rdiff-backup/rdiff_backup/restore.py @@ -21,9 +21,8 @@ from __future__ import generators import tempfile, os -from log import Log -import Globals, Time, Rdiff, Hardlink, FilenameMapping, SetConnections, \ - rorpiter, selection, destructive_stepping, rpath, lazy +import Globals, Time, Rdiff, Hardlink, FilenameMapping, rorpiter, \ + selection, rpath, log, backup, static, robust, metadata # This should be set to selection.Select objects over the source and @@ -31,66 +30,24 @@ import Globals, Time, Rdiff, Hardlink, FilenameMapping, SetConnections, \ _select_source = None _select_mirror = None -class RestoreError(Exception): pass - -def Restore(inc_rpath, mirror, target, rest_time): - """Recursively restore inc_rpath and mirror to target at rest_time - - Like restore_recusive below, but with a more friendly - interface (it converts to DSRPaths if necessary, finds the inc - files with the appropriate base, and makes rid). +# This will be set to the time of the current mirror +_mirror_time = None +# This will be set to the exact time to restore to (not restore_to_time) +_rest_time = None - rest_time is the time in seconds to restore to; - inc_rpath should not be the name of an increment file, but the - increment file shorn of its suffixes and thus should have the - same index as mirror. +class RestoreError(Exception): pass - """ - if not isinstance(target, destructive_stepping.DSRPath): - target = destructive_stepping.DSRPath(None, target) - - mirror_time = get_mirror_time() - rest_time = get_rest_time(rest_time, mirror_time) - inc_list = get_inclist(inc_rpath) - rid = RestoreIncrementData(inc_rpath.index, inc_rpath, inc_list) - rid.sortincseq(rest_time, mirror_time) - check_hardlinks(rest_time) - restore_recursive(inc_rpath.index, mirror, rid, target, - rest_time, mirror_time) - -def get_mirror_time(): - """Return the time (in seconds) of latest mirror""" - current_mirror_incs = get_inclist(Globals.rbdir.append("current_mirror")) - if not current_mirror_incs: - Log.FatalError("Could not get time of current mirror") - elif len(current_mirror_incs) > 1: - Log("Warning, two different dates for current mirror found", 2) - return Time.stringtotime(current_mirror_incs[0].getinctime()) - -def get_rest_time(old_rest_time, mirror_time): - """If old_rest_time is between two increments, return older time - - There is a slightly tricky reason for doing this: The rest of - the code just ignores increments that are older than - rest_time. But sometimes we want to consider the very next - increment older than rest time, because rest_time will be - between two increments, and what was actually on the mirror - side will correspond to the older one. - - So here we assume all rdiff-backup events were recorded in - "increments" increments, and if its in-between we pick the - older one here. +def Restore(mirror_rp, inc_rpath, target, restore_to_time): + """Recursively restore mirror and inc_rpath to target at rest_time""" + MirrorS = mirror_rp.conn.restore.MirrorStruct + TargetS = target.conn.restore.TargetStruct - """ - base_incs = get_inclist(Globals.rbdir.append("increments")) - if not base_incs: return old_rest_time - inctimes = [Time.stringtotime(inc.getinctime()) for inc in base_incs] - inctimes.append(mirror_time) - older_times = filter(lambda time: time <= old_rest_time, inctimes) - if older_times: return max(older_times) - else: # restore time older than oldest increment, just return that - return min(inctimes) + MirrorS.set_mirror_and_rest_times(restore_to_time) + MirrorS.initialize_rf_cache(mirror_rp, inc_rpath) + target_iter = TargetS.get_initial_iter(target) + diff_iter = MirrorS.get_diffs(target_iter) + TargetS.patch(target, diff_iter) def get_inclist(inc_rpath): """Returns increments with given base""" @@ -113,179 +70,271 @@ def get_inclist(inc_rpath): inc_list.append(inc) return inc_list -def check_hardlinks(rest_time): - """Check for hard links and enable hard link support if found""" - if (Globals.preserve_hardlinks != 0 and - Hardlink.retrieve_final(rest_time)): - Log("Hard link information found, attempting to preserve " - "hard links.", 5) - SetConnections.UpdateGlobal('preserve_hardlinks', 1) - else: SetConnections.UpdateGlobal('preserve_hardlinks', None) +def ListChangedSince(mirror_rp, inc_rp, restore_to_time): + """List the changed files under mirror_rp since rest time""" + MirrorS = mirror_rp.conn.restore.MirrorStruct + MirrorS.set_mirror_and_rest_times(restore_to_time) + MirrorS.initialize_rf_cache(mirror_rp, inc_rp) + + cur_iter = MirrorS.get_mirror_rorp_iter(_mirror_time, 1) + old_iter = MirrorS.get_mirror_rorp_iter(_rest_time, 1) + collated = rorpiter.Collate2Iters(old_iter, cur_iter) + for old_rorp, cur_rorp in collated: + if not old_rorp: change = "new" + elif not cur_rorp: change = "deleted" + elif old_rorp == cur_rorp: continue + else: change = "changed" + path_desc = (old_rorp and old_rorp.get_indexpath() or + cur_rorp.get_indexpath()) + print "%-7s %s" % (change, path_desc) + + +class MirrorStruct: + """Hold functions to be run on the mirror side""" + def set_mirror_and_rest_times(cls, restore_to_time): + """Set global variabels _mirror_time and _rest_time on mirror conn""" + global _mirror_time, _rest_time + _mirror_time = cls.get_mirror_time() + _rest_time = cls.get_rest_time(restore_to_time) + + def get_mirror_time(cls): + """Return time (in seconds) of latest mirror""" + cur_mirror_incs = get_inclist(Globals.rbdir.append("current_mirror")) + if not cur_mirror_incs: + log.Log.FatalError("Could not get time of current mirror") + elif len(cur_mirror_incs) > 1: + log.Log("Warning, two different times for current mirror found", 2) + return cur_mirror_incs[0].getinctime() + + def get_rest_time(cls, restore_to_time): + """Return older time, if restore_to_time is in between two inc times + + There is a slightly tricky reason for doing this: The rest of the + code just ignores increments that are older than restore_to_time. + But sometimes we want to consider the very next increment older + than rest time, because rest_time will be between two increments, + and what was actually on the mirror side will correspond to the + older one. + + So here we assume all rdiff-backup events were recorded in + "increments" increments, and if it's in-between we pick the + older one here. -def restore_recursive(index, mirror, rid, target, time, mirror_time): - """Recursive restore function. + """ + global _rest_time + base_incs = get_inclist(Globals.rbdir.append("increments")) + if not base_incs: return _mirror_time + inctimes = [inc.getinctime() for inc in base_incs] + inctimes.append(_mirror_time) + older_times = filter(lambda time: time <= restore_to_time, inctimes) + if older_times: return max(older_times) + else: # restore time older than oldest increment, just return that + return min(inctimes) + + def initialize_rf_cache(cls, mirror_base, inc_base): + """Set cls.rf_cache to CachedRF object""" + inc_list = get_inclist(inc_base) + rf = RestoreFile(mirror_base, inc_base, get_inclist(inc_base)) + cls.mirror_base, cls.inc_base = mirror_base, inc_base + cls.root_rf = rf + cls.rf_cache = CachedRF(rf) + + def get_mirror_rorp_iter(cls, rest_time = None, require_metadata = None): + """Return iter of mirror rps at given restore time + + Usually we can use the metadata file, but if this is + unavailable, we may have to build it from scratch. - rid is a RestoreIncrementData object whose inclist is already - sortedincseq'd, and target is the dsrp to restore to. + """ + if rest_time is None: rest_time = _rest_time + metadata_iter = metadata.GetMetadata_at_time(Globals.rbdir, + rest_time, restrict_index = cls.mirror_base.index) + if metadata_iter: return metadata_iter + if require_metadata: log.Log.FatalError("Mirror metadata not found") + log.Log("Warning: Mirror metadata not found, " + "reading from directory", 2) + return cls.get_rorp_iter_from_rf(cls.root_rf) + + def get_rorp_iter_from_rf(cls, rf): + """Recursively yield mirror rorps from rf""" + rorp = rf.get_attribs() + yield rorp + if rorp.isdir(): + for sub_rf in rf.yield_sub_rfs(): + for rorp in yield_attribs(sub_rf): yield rorp + + def subtract_indicies(cls, index, rorp_iter): + """Subtract index from index of each rorp in rorp_iter + + subtract_indicies and add_indicies are necessary because we + may not be restoring from the root index. - Note that target may have a different index than mirror and - rid, because we may be restoring a file whose index is, say - ('foo','bar') to a target whose path does not contain - "foo/bar". + """ + if index == (): return rorp_iter + def get_iter(): + for rorp in rorp_iter: + assert rorp.index[:len(index)] == index, (rorp.index, index) + rorp.index = rorp.index[len(index):] + yield rorp + return get_iter() - """ - assert isinstance(target, destructive_stepping.DSRPath) - assert mirror.index == rid.index + def get_diffs(cls, target_iter): + """Given rorp iter of target files, return diffs - target_finalizer = rorpiter.IterTreeReducer( - rorpiter.DestructiveSteppingFinalizer, ()) - for rcd in yield_rcds(rid.index, mirror, rid, target, time, mirror_time): - rcd.RestoreFile() - #if rcd.mirror: mirror_finalizer(rcd.index, rcd.mirror) - target_finalizer(rcd.target.index, rcd.target) - target_finalizer.Finish() + Here the target_iter doesn't contain any actual data, just + attribute listings. Thus any diffs we generate will be + snapshots. -def yield_rcds(index, mirrorrp, rid, target, rest_time, mirror_time): - """Iterate RestoreCombinedData objects starting with given args + """ + mir_iter = cls.subtract_indicies(cls.mirror_base.index, + cls.get_mirror_rorp_iter()) + collated = rorpiter.Collate2Iters(mir_iter, target_iter) + return cls.get_diffs_from_collated(collated) + + def get_diffs_from_collated(cls, collated): + """Get diff iterator from collated""" + for mir_rorp, target_rorp in collated: + if Globals.preserve_hardlinks: + if mir_rorp: Hardlink.add_rorp(mir_rorp, source = 1) + if target_rorp: Hardlink.add_rorp(target_rorp, source = 0) + + if (not target_rorp or not mir_rorp or + not mir_rorp == target_rorp or + (Globals.preserve_hardlinks and not + Hardlink.rorp_eq(mir_rorp, target_rorp))): + yield cls.get_diff(mir_rorp, target_rorp) + + def get_diff(cls, mir_rorp, target_rorp): + """Get a diff for mir_rorp at time""" + if not mir_rorp: mir_rorp = rpath.RORPath(target_rorp.index) + elif Globals.preserve_hardlinks and Hardlink.islinked(mir_rorp): + mir_rorp.flaglinked(Hardlink.get_link_index(mir_rorp)) + elif mir_rorp.isreg(): + expanded_index = cls.mirror_base.index + mir_rorp.index + mir_rorp.setfile(cls.rf_cache.get_fp(expanded_index)) + mir_rorp.set_attached_filetype('snapshot') + return mir_rorp + +static.MakeClass(MirrorStruct) + + +class TargetStruct: + """Hold functions to be run on the target side when restoring""" + def get_initial_iter(cls, target): + """Return a selection object iterating the rorpaths in target""" + return selection.Select(target).set_iter() + + def patch(cls, target, diff_iter): + """Patch target with the diffs from the mirror side + + This function was already written for use when backing up, so + just use that. - rid is a RestoreCombinedData object. target is an rpath where - the created file should go. + """ + backup.DestinationStruct.patch(target, diff_iter) - In this case the "mirror" directory is treated as the source, - and we are actually copying stuff onto what Select considers - the source directory. +static.MakeClass(TargetStruct) - """ - select_result = _select_mirror.Select(target) - if select_result == 0: return - - if mirrorrp and not _select_source.Select(mirrorrp): - mirrorrp = None - rcd = RestoreCombinedData(rid, mirrorrp, target) - - if mirrorrp and mirrorrp.isdir() or \ - rid and rid.inc_rpath and rid.inc_rpath.isdir(): - sub_rcds = yield_sub_rcds(index, mirrorrp, rid, - target, rest_time, mirror_time) - else: sub_rcds = None - - if select_result == 1: - yield rcd - if sub_rcds: - for sub_rcd in sub_rcds: yield sub_rcd - elif select_result == 2: - if sub_rcds: - try: first = sub_rcds.next() - except StopIteration: return # no tuples found inside, skip - yield rcd - yield first - for sub_rcd in sub_rcds: yield sub_rcd - -def yield_sub_rcds(index, mirrorrp, rid, target, rest_time, mirror_time): - """Yield collated tuples from inside given args""" - if not check_dir_exists(mirrorrp, rid): return - mirror_iter = yield_mirrorrps(mirrorrp) - rid_iter = yield_rids(rid, rest_time, mirror_time) - - for indexed_tup in rorpiter.CollateIterators(mirror_iter, rid_iter): - index = indexed_tup.index - new_mirrorrp, new_rid = indexed_tup - for rcd in yield_rcds(index, new_mirrorrp, new_rid, - target.append(index[-1]), rest_time, mirror_time): - yield rcd - -def check_dir_exists(mirrorrp, rid): - """Return true if target should be a directory""" - if rid and rid.inc_list: - # Incs say dir if last (earliest) one is a dir increment - return rid.inc_list[-1].getinctype() == "dir" - elif mirrorrp: return mirrorrp.isdir() # if no incs, copy mirror - else: return None - -def yield_mirrorrps(mirrorrp): - """Yield mirrorrps underneath given mirrorrp""" - if mirrorrp and mirrorrp.isdir(): - if Globals.quoting_enabled: - for rp in selection.get_quoted_dir_children(mirrorrp): - yield rp - else: - dirlist = mirrorrp.listdir() - dirlist.sort() - for filename in dirlist: yield mirrorrp.append(filename) -def yield_rids(rid, rest_time, mirror_time): - """Yield RestoreIncrementData objects within given rid dir +class CachedRF: + """Store RestoreFile objects until they are needed + + The code above would like to pretend it has random access to RFs, + making one for a particular index at will. However, in general + this involves listing and filtering a directory, which can get + expensive. - If the rid doesn't correspond to a directory, don't yield any - elements. If there are increments whose corresponding base - doesn't exist, the first element will be None. All the rpaths - involved correspond to files in the increment directory. + Thus, when a CachedRF retrieves an RestoreFile, it creates all the + RFs of that directory at the same time, and doesn't have to + recalculate. It assumes the indicies will be in order, so the + cache is deleted if a later index is requested. """ - if not rid or not rid.inc_rpath or not rid.inc_rpath.isdir(): return - rid_dict = {} # dictionary of basenames:rids - dirlist = rid.inc_rpath.listdir() - if Globals.quoting_enabled: - dirlist = [FilenameMapping.unquote(fn) for fn in dirlist] - - def affirm_dict_indexed(basename): - """Make sure the rid dictionary has given basename as key""" - if not rid_dict.has_key(basename): - rid_dict[basename] = RestoreIncrementData( - rid.index + (basename,), None, []) # init with empty rid - - def add_to_dict(filename): - """Add filename to the inc tuple dictionary""" - rp = rid.inc_rpath.append(filename) - if Globals.quoting_enabled: rp.quote_path() - if rp.isincfile() and rp.getinctype() != 'data': - basename = rp.getincbase_str() - affirm_dict_indexed(basename) - rid_dict[basename].inc_list.append(rp) - elif rp.isdir(): - affirm_dict_indexed(filename) - rid_dict[filename].inc_rpath = rp - - for filename in dirlist: add_to_dict(filename) - keys = rid_dict.keys() - keys.sort() - - # sortincseq now to avoid descending .missing directories later - for key in keys: - rid = rid_dict[key] - if rid.inc_rpath or rid.inc_list: - rid.sortincseq(rest_time, mirror_time) - yield rid - - -class RestoreIncrementData: - """Contains information about a specific index from the increments dir - - This is just a container class, used because it would be easier to - work with than an IndexedTuple. + def __init__(self, root_rf): + """Initialize CachedRF, self.rf_list variable""" + self.root_rf = root_rf + self.rf_list = [] # list should filled in index order + + def list_rfs_in_cache(self, index): + """Used for debugging, return indicies of cache rfs for printing""" + s1 = "-------- Cached RF for %s -------" % (index,) + s2 = " ".join([str(rf.index) for rf in self.rf_list]) + s3 = "--------------------------" + return "\n".join((s1, s2, s3)) + + def get_rf(self, index): + """Return RestoreFile of given index""" + while 1: + if not self.rf_list: self.add_rfs(index) + rf = self.rf_list.pop(0) + if rf.index < index: continue + elif rf.index == index: return rf + self.rf_list.insert(0, rf) + self.add_rfs(index) + + def get_fp(self, index): + """Return the file object (for reading) of given index""" + return self.get_rf(index).get_restore_fp() + + def add_rfs(self, index): + """Given index, add the rfs in that same directory""" + if not index: return self.root_rf + parent_index = index[:-1] + temp_rf = RestoreFile(self.root_rf.mirror_rp.new_index(parent_index), + self.root_rf.inc_rp.new_index(parent_index), []) + new_rfs = list(temp_rf.yield_sub_rfs()) + assert new_rfs, "No RFs added for index %s" % index + self.rf_list[0:0] = new_rfs + +class RestoreFile: + """Hold data about a single mirror file and its related increments + + self.relevant_incs will be set to a list of increments that matter + for restoring a regular file. If the patches are to mirror_rp, it + will be the first element in self.relevant.incs """ - def __init__(self, index, inc_rpath, inc_list): - self.index = index - self.inc_rpath = inc_rpath - self.inc_list = inc_list - - def sortincseq(self, rest_time, mirror_time): - """Sort self.inc_list sequence, throwing away irrelevant increments""" - if not self.inc_list or rest_time >= mirror_time: - self.inc_list = [] + def __init__(self, mirror_rp, inc_rp, inc_list): + assert mirror_rp.index == inc_rp.index, (mirror_rp, inc_rp) + self.index = mirror_rp.index + self.mirror_rp = mirror_rp + self.inc_rp, self.inc_list = inc_rp, inc_list + self.set_relevant_incs() + + def relevant_incs_string(self): + """Return printable string of relevant incs, used for debugging""" + l = ["---- Relevant incs for %s" % ("/".join(self.index),)] + l.extend(["%s %s %s" % (inc.getinctype(), inc.lstat(), inc.path) + for inc in self.relevant_incs]) + l.append("--------------------------------") + return "\n".join(l) + + def set_relevant_incs(self): + """Set self.relevant_incs to increments that matter for restoring + + relevant_incs is sorted newest first. If mirror_rp matters, + it will be (first) in relevant_incs. + + """ + self.mirror_rp.inc_type = 'snapshot' + self.mirror_rp.inc_compressed = 0 + if not self.inc_list or _rest_time >= _mirror_time: + self.relevant_incs = [self.mirror_rp] return - newer_incs = self.get_newer_incs(rest_time, mirror_time) + newer_incs = self.get_newer_incs() i = 0 while(i < len(newer_incs)): # Only diff type increments require later versions if newer_incs[i].getinctype() != "diff": break i = i+1 - self.inc_list = newer_incs[:i+1] - self.inc_list.reverse() # return in reversed order (latest first) + self.relevant_incs = newer_incs[:i+1] + if (not self.relevant_incs or + self.relevant_incs[-1].getinctype() == "diff"): + self.relevant_incs.append(self.mirror_rp) + self.relevant_incs.reverse() # return in reversed order - def get_newer_incs(self, rest_time, mirror_time): + def get_newer_incs(self): """Return list of newer incs sorted by time (increasing) Also discard increments older than rest_time (rest_time we are @@ -295,94 +344,119 @@ class RestoreIncrementData: """ incpairs = [] for inc in self.inc_list: - time = Time.stringtotime(inc.getinctime()) - if time >= rest_time: incpairs.append((time, inc)) + time = inc.getinctime() + if time >= _rest_time: incpairs.append((time, inc)) incpairs.sort() return [pair[1] for pair in incpairs] - -class RestoreCombinedData: - """Combine index information from increment and mirror directories + def get_attribs(self): + """Return RORP with restored attributes, but no data - This is similar to RestoreIncrementData but has mirror information - also. - - """ - def __init__(self, rid, mirror, target): - """Init - set values from one or both if they exist - - mirror and target are DSRPaths of the corresponding files in - the mirror and target directory respectively. rid is a - RestoreIncrementData as defined above + This should only be necessary if the metadata file is lost for + some reason. Otherwise the file provides all data. The size + will be wrong here, because the attribs may be taken from + diff. """ - if rid: - self.index = rid.index - self.inc_rpath = rid.inc_rpath - self.inc_list = rid.inc_list - if mirror: - self.mirror = mirror - assert mirror.index == self.index - else: self.mirror = None - elif mirror: - self.index = mirror.index - self.mirror = mirror - self.inc_list = [] - self.inc_rpath = None - else: assert None, "neither rid nor mirror given" - self.target = target - - def RestoreFile(self): - """Non-recursive restore function """ - if not self.inc_list and not (self.mirror and self.mirror.lstat()): - return # no increments were applicable - self.log() - - if self.restore_hardlink(): return - - if not self.inc_list or self.inc_list[0].getinctype() == "diff": - assert self.mirror and self.mirror.lstat(), \ - "No base to go with incs for %s" % self.target.path - rpath.copy_with_attribs(self.mirror, self.target) - for inc in self.inc_list: self.applyinc(inc, self.target) - - def log(self): - """Log current restore action""" - inc_string = ','.join([inc.path for inc in self.inc_list]) - Log("Restoring %s with increments %s to %s" % - (self.mirror and self.mirror.path, - inc_string, self.target.path), 5) - - def restore_hardlink(self): - """Hard link target and return true if hard linking appropriate""" - if (Globals.preserve_hardlinks and - Hardlink.restore_link(self.index, self.target)): - rpath.copy_attribs(self.inc_list and self.inc_list[-1] or - self.mirror, self.target) - return 1 - return None - - def applyinc(self, inc, target): - """Apply increment rp inc to targetrp target""" - Log("Applying increment %s to %s" % (inc.path, target.path), 6) - inctype = inc.getinctype() - if inctype == "diff": - if not target.lstat(): - raise RestoreError("Bad increment sequence at " + inc.path) - Rdiff.patch_action(target, inc, - delta_compressed = inc.isinccompressed() - ).execute() - elif inctype == "dir": - if not target.isdir(): - if target.lstat(): - raise RestoreError("File %s already exists" % target.path) - target.mkdir() - elif inctype == "missing": return - elif inctype == "snapshot": - if inc.isinccompressed(): - target.write_from_fileobj(inc.open("rb", compress = 1)) - else: rpath.copy(inc, target) - else: raise RestoreError("Unknown inctype %s" % inctype) - rpath.copy_attribs(inc, target) - + last_inc = self.relevant_incs[-1] + if last_inc.getinctype() == 'missing': return rpath.RORPath(self.index) + + rorp = last_inc.getRORPath() + rorp.index = self.index + if last_inc.getinctype() == 'dir': rorp.data['type'] = 'dir' + return rorp + + def get_restore_fp(self): + """Return file object of restored data""" + assert self.relevant_incs[-1].isreg(), "Not a regular file" + current_fp = self.get_first_fp() + for inc_diff in self.relevant_incs[1:]: + log.Log("Applying patch %s" % (inc_diff.get_indexpath(),), 7) + assert inc_diff.getinctype() == 'diff' + delta_fp = inc_diff.open("rb", inc_diff.isinccompressed()) + new_fp = tempfile.TemporaryFile() + Rdiff.write_patched_fp(current_fp, delta_fp, new_fp) + new_fp.seek(0) + current_fp = new_fp + return current_fp + + def get_first_fp(self): + """Return first file object from relevant inc list""" + first_inc = self.relevant_incs[0] + assert first_inc.getinctype() == 'snapshot' + if not first_inc.isinccompressed(): return first_inc.open("rb") + + # current_fp must be a real (uncompressed) file + current_fp = tempfile.TemporaryFile() + fp = first_inc.open("rb", compress = 1) + rpath.copyfileobj(fp, current_fp) + assert not fp.close() + current_fp.seek(0) + return current_fp + + def yield_sub_rfs(self): + """Return RestoreFiles under current RestoreFile (which is dir)""" + assert self.mirror_rp.isdir() or self.inc_rp.isdir() + mirror_iter = self.yield_mirrorrps(self.mirror_rp) + inc_pair_iter = self.yield_inc_complexes(self.inc_rp) + collated = rorpiter.Collate2Iters(mirror_iter, inc_pair_iter) + + for mirror_rp, inc_pair in collated: + if not inc_pair: + inc_rp = self.inc_rp.new_index(mirror_rp.index) + if Globals.quoting_enabled: inc_rp.quote_path() + inc_list = [] + else: inc_rp, inc_list = inc_pair + if not mirror_rp: + mirror_rp = self.mirror_rp.new_index(inc_rp.index) + if Globals.quoting_enabled: mirror_rp.quote_path() + yield RestoreFile(mirror_rp, inc_rp, inc_list) + + def yield_mirrorrps(self, mirrorrp): + """Yield mirrorrps underneath given mirrorrp""" + if mirrorrp and mirrorrp.isdir(): + if Globals.quoting_enabled: + for rp in selection.get_quoted_dir_children(mirrorrp): + if rp.index != ('rdiff-backup-data',): yield rp + else: + dirlist = mirrorrp.listdir() + dirlist.sort() + for filename in dirlist: + rp = mirrorrp.append(filename) + if rp.index != ('rdiff-backup-data',): yield rp + + def yield_inc_complexes(self, inc_rpath): + """Yield (sub_inc_rpath, inc_list) IndexedTuples from given inc_rpath + + Finds pairs under directory inc_rpath. sub_inc_rpath will just be + the prefix rp, while the rps in inc_list should actually exist. + """ + if not inc_rpath.isdir(): return + inc_dict = {} # dictionary of basenames:inc_lists + dirlist = robust.listrp(inc_rpath) + if Globals.quoting_enabled: + dirlist = [FilenameMapping.unquote(fn) for fn in dirlist] + + def affirm_dict_indexed(basename): + """Make sure the rid dictionary has given basename as key""" + if not inc_dict.has_key(basename): + sub_inc_rp = inc_rpath.append(basename) + if Globals.quoting_enabled: sub_inc_rp.quote_path() + inc_dict[basename] = rorpiter.IndexedTuple(sub_inc_rp.index, + (sub_inc_rp, [])) + + def add_to_dict(filename): + """Add filename to the inc tuple dictionary""" + rp = inc_rpath.append(filename) + if Globals.quoting_enabled: rp.quote_path() + if rp.isincfile() and rp.getinctype() != 'data': + basename = rp.getincbase_str() + affirm_dict_indexed(basename) + inc_dict[basename][1].append(rp) + elif rp.isdir(): affirm_dict_indexed(filename) + + for filename in dirlist: add_to_dict(filename) + keys = inc_dict.keys() + keys.sort() + for key in keys: yield inc_dict[key] -- cgit v1.2.1