1# Copyright 2005 Ben Escoto 2# 3# This file is part of rdiff-backup. 4# 5# rdiff-backup is free software; you can redistribute it and/or modify 6# under the terms of the GNU General Public License as published by the 7# Free Software Foundation; either version 2 of the License, or (at your 8# option) any later version. 9# 10# rdiff-backup is distributed in the hope that it will be useful, but 11# WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with rdiff-backup; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18# 02110-1301, USA 19"""Contains a file wrapper that returns a hash on close""" 20 21import hashlib 22from . import Globals 23 24 25class FileWrapper: 26 """Wrapper around a file-like object 27 28 Only use this with files that will be read through in a single 29 pass and then closed. (There is no seek().) When you close it, 30 return value will be a Report. 31 32 Currently this just calculates a sha1sum of the datastream. 33 34 """ 35 36 def __init__(self, fileobj): 37 self.fileobj = fileobj 38 self.sha1 = hashlib.sha1() 39 self.closed = 0 40 41 def read(self, length=-1): 42 assert not self.closed 43 buf = self.fileobj.read(length) 44 self.sha1.update(buf) 45 return buf 46 47 def close(self): 48 return Report(self.fileobj.close(), self.sha1.hexdigest()) 49 50 51class Report: 52 """Hold final information about a byte stream""" 53 54 def __init__(self, close_val, sha1_digest): 55 # FIXME this is a strange construct because it looks like the fileobj 56 # wrapped in a FileWrapper already returns a Report as closing value, 57 # which we can't wrap again in a Report, so we only check that the 58 # hash values do fit. 59 if isinstance(close_val, Report): 60 assert close_val.sha1_digest == sha1_digest, \ 61 "Hashes from return code %s and given %s don't match" % \ 62 (close_val.sha1_digest, sha1_digest) 63 else: 64 assert not close_val, "Return code %s of type %s isn't null" % \ 65 (close_val, type(close_val)) 66 self.sha1_digest = sha1_digest 67 68 69def compute_sha1(rp, compressed=0): 70 """Return the hex sha1 hash of given rpath""" 71 assert rp.conn is Globals.local_connection # inefficient not to do locally 72 digest = compute_sha1_fp(rp.open("rb", compressed)) 73 rp.set_sha1(digest) 74 return digest 75 76 77def compute_sha1_fp(fp, compressed=0): 78 """Return hex sha1 hash of given file-like object""" 79 blocksize = Globals.blocksize 80 fw = FileWrapper(fp) 81 while 1: 82 if not fw.read(blocksize): 83 break 84 return fw.close().sha1_digest 85