~moritzm/duplicity/duplicity

« back to all changes in this revision

Viewing changes to duplicity/librsync.py

  • Committer: bescoto
  • Date: 2002-10-29 01:49:46 UTC
  • Revision ID: vcs-imports@canonical.com-20021029014946-3m4rmm5plom7pl6q
Initial checkin

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2002 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
# 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.
 
10
 
 
11
"""Provides a high-level interface to some librsync functions
 
12
 
 
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...
 
15
 
 
16
"""
 
17
 
 
18
import _librsync, types, array
 
19
 
 
20
blocksize = _librsync.RS_JOB_BLOCKSIZE
 
21
 
 
22
class librsyncError(Exception):
 
23
        """Signifies error in internal librsync processing (bad signature, etc.)
 
24
 
 
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.
 
29
 
 
30
        """
 
31
        pass
 
32
 
 
33
 
 
34
class LikeFile:
 
35
        """File-like object used by SigFile, DeltaFile, and PatchFile"""
 
36
        mode = "rb"
 
37
 
 
38
        # This will be replaced in subclasses by an object with
 
39
        # appropriate cycle() method
 
40
        maker = None
 
41
 
 
42
        def __init__(self, infile, need_seek = None):
 
43
                """LikeFile initializer - zero buffers, set eofs off"""
 
44
                self.check_file(infile, need_seek)
 
45
                self.infile = infile
 
46
                self.closed = self.infile_closed = None
 
47
                self.inbuf = ""
 
48
                self.outbuf = array.array('c')
 
49
                self.eof = self.infile_eof = None
 
50
 
 
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")
 
59
 
 
60
        def read(self, length = -1):
 
61
                """Build up self.outbuf, return first length bytes"""
 
62
                if length == -1:
 
63
                        while not self.eof: self._add_to_outbuf_once()
 
64
                        real_len = len(self.outbuf)
 
65
                else:
 
66
                        while not self.eof and len(self.outbuf) < length:
 
67
                                self._add_to_outbuf_once()
 
68
                        real_len = min(length, len(self.outbuf))
 
69
                        
 
70
                return_val = self.outbuf[:real_len].tostring()
 
71
                del self.outbuf[:real_len]
 
72
                return return_val
 
73
 
 
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)
 
81
 
 
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)
 
87
                        if not new_in:
 
88
                                self.infile_eof = 1
 
89
                                assert not self.infile.close()
 
90
                                self.infile_closed = 1
 
91
                                break
 
92
                        self.inbuf += new_in
 
93
 
 
94
        def close(self):
 
95
                """Close infile"""
 
96
                if not self.infile_closed: assert not self.infile.close()
 
97
                self.closed = 1
 
98
 
 
99
 
 
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
 
104
 
 
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.
 
107
 
 
108
                """
 
109
                LikeFile.__init__(self, infile)
 
110
                try: self.maker = _librsync.new_sigmaker()
 
111
                except _librsync.librsyncError, e: raise librsyncError(str(e))
 
112
 
 
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
 
117
 
 
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.
 
121
 
 
122
                """
 
123
                LikeFile.__init__(self, new_file)
 
124
                if type(signature) is types.StringType: sig_string = signature
 
125
                else:
 
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))
 
131
 
 
132
 
 
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
 
137
 
 
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.
 
141
 
 
142
                """
 
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))          
 
148
 
 
149
 
 
150
class SigGenerator:
 
151
        """Calculate signature.
 
152
 
 
153
        Input and output is same as SigFile, but the interface is like md5
 
154
        module, not filelike object
 
155
 
 
156
        """
 
157
        def __init__(self):
 
158
                """Return new signature instance"""
 
159
                try: self.sig_maker = _librsync.new_sigmaker()
 
160
                except _librsync.librsyncError, e: raise librsyncError(str(e))
 
161
                self.gotsig = None
 
162
                self.buffer = ""
 
163
                self.sig_string = ""
 
164
 
 
165
        def update(self, buf):
 
166
                """Add buf to data that signature will be calculated over"""
 
167
                if self.gotsig:
 
168
                        raise librsyncError("SigGenerator already provided signature")
 
169
                self.buffer += buf
 
170
                while len(self.buffer) >= blocksize:
 
171
                        if self.process_buffer():
 
172
                                raise librsyncError("Premature EOF received from sig_maker")
 
173
 
 
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
 
180
                return eof
 
181
 
 
182
        def getsig(self):
 
183
                """Return signature over given data"""
 
184
                while not self.process_buffer(): pass # keep running until eof
 
185
                return self.sig_string
 
186