1# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4; encoding:utf8 -*- 2# 3# Copyright 2002 Ben Escoto <ben@emerose.org> 4# Copyright 2007 Kenneth Loafman <kenneth@loafman.com> 5# 6# This file is part of duplicity. 7# 8# duplicity is free software; you can redistribute it and/or modify 9# under the terms of the GNU General Public License as published by 10# the Free Software Foundation; either version 2 of the License, or 11# (at your option) any later version. 12# 13# duplicity is distributed in the hope that it will be useful, but 14# WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16# General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with duplicity; if not, write to the Free Software Foundation, 20# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 22u"""Provides a high-level interface to some librsync functions 23 24This is a python wrapper around the lower-level _librsync module, 25which is written in C. The goal was to use C as little as possible... 26 27""" 28 29from builtins import object 30from builtins import str 31 32import array 33import os 34import sys 35 36from . import _librsync 37 38if os.environ.get(u'READTHEDOCS') == u'True': 39 import mock # pylint: disable=import-error 40 import duplicity 41 duplicity._librsync = mock.MagicMock() 42 43blocksize = _librsync.RS_JOB_BLOCKSIZE 44 45 46class librsyncError(Exception): 47 u"""Signifies error in internal librsync processing (bad signature, etc.) 48 49 underlying _librsync.librsyncError's are regenerated using this 50 class because the C-created exceptions are by default 51 unPickleable. There is probably a way to fix this in _librsync, 52 but this scheme was easier. 53 54 """ 55 pass 56 57 58class LikeFile(object): 59 u"""File-like object used by SigFile, DeltaFile, and PatchFile""" 60 mode = u"rb" 61 62 # This will be replaced in subclasses by an object with 63 # appropriate cycle() method 64 maker = None 65 66 def __init__(self, infile, need_seek=None): 67 u"""LikeFile initializer - zero buffers, set eofs off""" 68 self.check_file(infile, need_seek) 69 self.infile = infile 70 self.closed = self.infile_closed = None 71 self.inbuf = b"" 72 self.outbuf = array.array(u'b') 73 self.eof = self.infile_eof = None 74 75 def check_file(self, file, need_seek=None): 76 u"""Raise type error if file doesn't have necessary attributes""" 77 if not hasattr(file, u"read"): 78 raise TypeError(u"Basis file must have a read() method") 79 if not hasattr(file, u"close"): 80 raise TypeError(u"Basis file must have a close() method") 81 if need_seek and not hasattr(file, u"seek"): 82 raise TypeError(u"Basis file must have a seek() method") 83 84 def read(self, length=-1): 85 u"""Build up self.outbuf, return first length bytes""" 86 if length == -1: 87 while not self.eof: 88 self._add_to_outbuf_once() 89 real_len = len(self.outbuf) 90 else: 91 while not self.eof and len(self.outbuf) < length: 92 self._add_to_outbuf_once() 93 real_len = min(length, len(self.outbuf)) 94 95 if sys.version_info.major >= 3: 96 return_val = self.outbuf[:real_len].tobytes() 97 else: 98 return_val = self.outbuf[:real_len].tostring() 99 del self.outbuf[:real_len] 100 return return_val 101 102 def _add_to_outbuf_once(self): 103 u"""Add one cycle's worth of output to self.outbuf""" 104 if not self.infile_eof: 105 self._add_to_inbuf() 106 try: 107 self.eof, len_inbuf_read, cycle_out = self.maker.cycle(self.inbuf) 108 except _librsync.librsyncError as e: 109 raise librsyncError(str(e)) 110 self.inbuf = self.inbuf[len_inbuf_read:] 111 if sys.version_info.major >= 3: 112 self.outbuf.frombytes(cycle_out) 113 else: 114 self.outbuf.fromstring(cycle_out) 115 116 def _add_to_inbuf(self): 117 u"""Make sure len(self.inbuf) >= blocksize""" 118 assert not self.infile_eof 119 while len(self.inbuf) < blocksize: 120 new_in = self.infile.read(blocksize) 121 if not new_in: 122 self.infile_eof = 1 123 assert not self.infile.close() 124 self.infile_closed = 1 125 break 126 self.inbuf += new_in 127 128 def close(self): 129 u"""Close infile""" 130 if not self.infile_closed: 131 assert not self.infile.close() 132 self.closed = 1 133 134 135class SigFile(LikeFile): 136 u"""File-like object which incrementally generates a librsync signature""" 137 def __init__(self, infile, blocksize=_librsync.RS_DEFAULT_BLOCK_LEN): 138 u"""SigFile initializer - takes basis file 139 140 basis file only needs to have read() and close() methods. It 141 will be closed when we come to the end of the signature. 142 143 """ 144 LikeFile.__init__(self, infile) 145 try: 146 self.maker = _librsync.new_sigmaker(blocksize) 147 except _librsync.librsyncError as e: 148 raise librsyncError(str(e)) 149 150 151class DeltaFile(LikeFile): 152 u"""File-like object which incrementally generates a librsync delta""" 153 def __init__(self, signature, new_file): 154 u"""DeltaFile initializer - call with signature and new file 155 156 Signature can either be a string or a file with read() and 157 close() methods. New_file also only needs to have read() and 158 close() methods. It will be closed when self is closed. 159 160 """ 161 LikeFile.__init__(self, new_file) 162 if isinstance(signature, bytes): 163 sig_string = signature 164 else: 165 self.check_file(signature) 166 sig_string = signature.read() 167 assert not signature.close() 168 try: 169 self.maker = _librsync.new_deltamaker(sig_string) 170 except _librsync.librsyncError as e: 171 raise librsyncError(str(e)) 172 173 174class PatchedFile(LikeFile): 175 u"""File-like object which applies a librsync delta incrementally""" 176 def __init__(self, basis_file, delta_file): 177 u"""PatchedFile initializer - call with basis delta 178 179 Here basis_file must be a true Python file, because we may 180 need to seek() around in it a lot, and this is done in C. 181 delta_file only needs read() and close() methods. 182 183 """ 184 LikeFile.__init__(self, delta_file) 185 try: 186 basis_file.fileno() 187 except: 188 u""" tempfile.TemporaryFile() only guarantees a true file 189 object on posix platforms. on cygwin/windows a file-like 190 object whose file attribute is the underlying true file 191 object is returned. 192 """ 193 if hasattr(basis_file, u'file') and hasattr(basis_file.file, u'fileno'): 194 basis_file = basis_file.file 195 else: 196 raise TypeError(_(u"basis_file must be a (true) file or an object whose " 197 u"file attribute is the underlying true file object")) 198 try: 199 self.maker = _librsync.new_patchmaker(basis_file) 200 except _librsync.librsyncError as e: 201 raise librsyncError(str(e)) 202 203 204class SigGenerator(object): 205 u"""Calculate signature. 206 207 Input and output is same as SigFile, but the interface is like md5 208 module, not filelike object 209 210 """ 211 def __init__(self, blocksize=_librsync.RS_DEFAULT_BLOCK_LEN): 212 u"""Return new signature instance""" 213 try: 214 self.sig_maker = _librsync.new_sigmaker(blocksize) 215 except _librsync.librsyncError as e: 216 raise librsyncError(str(e)) 217 self.gotsig = None 218 self.buffer = b"" 219 self.sigstring_list = [] 220 221 def update(self, buf): 222 u"""Add buf to data that signature will be calculated over""" 223 if self.gotsig: 224 raise librsyncError(u"SigGenerator already provided signature") 225 self.buffer += buf 226 while len(self.buffer) >= blocksize: 227 if self.process_buffer(): 228 raise librsyncError(u"Premature EOF received from sig_maker") 229 230 def process_buffer(self): 231 u"""Run self.buffer through sig_maker, add to self.sig_string""" 232 try: 233 eof, len_buf_read, cycle_out = self.sig_maker.cycle(self.buffer) 234 except _librsync.librsyncError as e: 235 raise librsyncError(str(e)) 236 self.buffer = self.buffer[len_buf_read:] 237 self.sigstring_list.append(cycle_out) 238 return eof 239 240 def getsig(self): 241 u"""Return signature over given data""" 242 while not self.process_buffer(): 243 pass # keep running until eof 244 return b''.join(self.sigstring_list) 245