1
# Copyright 2002 Ben Escoto
3
# This file is part of rdiff-backup.
5
# rdiff-backup is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA
8
# 02139, USA; either version 2 of the License, or (at your option) any
9
# later version; incorporated herein by reference.
11
"""Provides a high-level interface to some librsync functions
13
This is a python wrapper around the lower-level _librsync module,
14
which is written in C. The goal was to use C as little as possible...
18
import _librsync, types, array
20
blocksize = _librsync.RS_JOB_BLOCKSIZE
22
class librsyncError(Exception):
23
"""Signifies error in internal librsync processing (bad signature, etc.)
25
underlying _librsync.librsyncError's are regenerated using this
26
class because the C-created exceptions are by default
27
unPickleable. There is probably a way to fix this in _librsync,
28
but this scheme was easier.
35
"""File-like object used by SigFile, DeltaFile, and PatchFile"""
38
# This will be replaced in subclasses by an object with
39
# appropriate cycle() method
42
def __init__(self, infile, need_seek = None):
43
"""LikeFile initializer - zero buffers, set eofs off"""
44
self.check_file(infile, need_seek)
46
self.closed = self.infile_closed = None
48
self.outbuf = array.array('c')
49
self.eof = self.infile_eof = None
51
def check_file(self, file, need_seek = None):
52
"""Raise type error if file doesn't have necessary attributes"""
53
if not hasattr(file, "read"):
54
raise TypeError("Basis file must have a read() method")
55
if not hasattr(file, "close"):
56
raise TypeError("Basis file must have a close() method")
57
if need_seek and not hasattr(file, "seek"):
58
raise TypeError("Basis file must have a seek() method")
60
def read(self, length = -1):
61
"""Build up self.outbuf, return first length bytes"""
63
while not self.eof: self._add_to_outbuf_once()
64
real_len = len(self.outbuf)
66
while not self.eof and len(self.outbuf) < length:
67
self._add_to_outbuf_once()
68
real_len = min(length, len(self.outbuf))
70
return_val = self.outbuf[:real_len].tostring()
71
del self.outbuf[:real_len]
74
def _add_to_outbuf_once(self):
75
"""Add one cycle's worth of output to self.outbuf"""
76
if not self.infile_eof: self._add_to_inbuf()
77
try: self.eof, len_inbuf_read, cycle_out = self.maker.cycle(self.inbuf)
78
except _librsync.librsyncError, e: raise librsyncError(str(e))
79
self.inbuf = self.inbuf[len_inbuf_read:]
80
self.outbuf.fromstring(cycle_out)
82
def _add_to_inbuf(self):
83
"""Make sure len(self.inbuf) >= blocksize"""
84
assert not self.infile_eof
85
while len(self.inbuf) < blocksize:
86
new_in = self.infile.read(blocksize)
89
assert not self.infile.close()
90
self.infile_closed = 1
96
if not self.infile_closed: assert not self.infile.close()
100
class SigFile(LikeFile):
101
"""File-like object which incrementally generates a librsync signature"""
102
def __init__(self, infile):
103
"""SigFile initializer - takes basis file
105
basis file only needs to have read() and close() methods. It
106
will be closed when we come to the end of the signature.
109
LikeFile.__init__(self, infile)
110
try: self.maker = _librsync.new_sigmaker()
111
except _librsync.librsyncError, e: raise librsyncError(str(e))
113
class DeltaFile(LikeFile):
114
"""File-like object which incrementally generates a librsync delta"""
115
def __init__(self, signature, new_file):
116
"""DeltaFile initializer - call with signature and new file
118
Signature can either be a string or a file with read() and
119
close() methods. New_file also only needs to have read() and
120
close() methods. It will be closed when self is closed.
123
LikeFile.__init__(self, new_file)
124
if type(signature) is types.StringType: sig_string = signature
126
self.check_file(signature)
127
sig_string = signature.read()
128
assert not signature.close()
129
try: self.maker = _librsync.new_deltamaker(sig_string)
130
except _librsync.librsyncError, e: raise librsyncError(str(e))
133
class PatchedFile(LikeFile):
134
"""File-like object which applies a librsync delta incrementally"""
135
def __init__(self, basis_file, delta_file):
136
"""PatchedFile initializer - call with basis delta
138
Here basis_file must be a true Python file, because we may
139
need to seek() around in it a lot, and this is done in C.
140
delta_file only needs read() and close() methods.
143
LikeFile.__init__(self, delta_file)
144
if type(basis_file) is not types.FileType:
145
raise TypeError("basis_file must be a (true) file")
146
try: self.maker = _librsync.new_patchmaker(basis_file)
147
except _librsync.librsyncError, e: raise librsyncError(str(e))
151
"""Calculate signature.
153
Input and output is same as SigFile, but the interface is like md5
154
module, not filelike object
158
"""Return new signature instance"""
159
try: self.sig_maker = _librsync.new_sigmaker()
160
except _librsync.librsyncError, e: raise librsyncError(str(e))
165
def update(self, buf):
166
"""Add buf to data that signature will be calculated over"""
168
raise librsyncError("SigGenerator already provided signature")
170
while len(self.buffer) >= blocksize:
171
if self.process_buffer():
172
raise librsyncError("Premature EOF received from sig_maker")
174
def process_buffer(self):
175
"""Run self.buffer through sig_maker, add to self.sig_string"""
176
try: eof, len_buf_read, cycle_out = self.sig_maker.cycle(self.buffer)
177
except _librsync.librsyncError, e: raise librsyncError(str(e))
178
self.buffer = self.buffer[len_buf_read:]
179
self.sig_string += cycle_out
183
"""Return signature over given data"""
184
while not self.process_buffer(): pass # keep running until eof
185
return self.sig_string