##// END OF EJS Templates
typing: add type hints to mpatch implementations...
Matt Harbison -
r50494:94a79703 default
parent child Browse files
Show More
@@ -1,48 +1,50 b''
1 # mpatch.py - CFFI implementation of mpatch.c
1 # mpatch.py - CFFI implementation of mpatch.c
2 #
2 #
3 # Copyright 2016 Maciej Fijalkowski <fijall@gmail.com>
3 # Copyright 2016 Maciej Fijalkowski <fijall@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8
8
9 from typing import List
10
9 from ..pure.mpatch import *
11 from ..pure.mpatch import *
10 from ..pure.mpatch import mpatchError # silence pyflakes
12 from ..pure.mpatch import mpatchError # silence pyflakes
11 from . import _mpatch # pytype: disable=import-error
13 from . import _mpatch # pytype: disable=import-error
12
14
13 ffi = _mpatch.ffi
15 ffi = _mpatch.ffi
14 lib = _mpatch.lib
16 lib = _mpatch.lib
15
17
16
18
17 @ffi.def_extern()
19 @ffi.def_extern()
18 def cffi_get_next_item(arg, pos):
20 def cffi_get_next_item(arg, pos):
19 all, bins = ffi.from_handle(arg)
21 all, bins = ffi.from_handle(arg)
20 container = ffi.new(b"struct mpatch_flist*[1]")
22 container = ffi.new(b"struct mpatch_flist*[1]")
21 to_pass = ffi.new(b"char[]", str(bins[pos]))
23 to_pass = ffi.new(b"char[]", str(bins[pos]))
22 all.append(to_pass)
24 all.append(to_pass)
23 r = lib.mpatch_decode(to_pass, len(to_pass) - 1, container)
25 r = lib.mpatch_decode(to_pass, len(to_pass) - 1, container)
24 if r < 0:
26 if r < 0:
25 return ffi.NULL
27 return ffi.NULL
26 return container[0]
28 return container[0]
27
29
28
30
29 def patches(text, bins):
31 def patches(text: bytes, bins: List[bytes]) -> bytes:
30 lgt = len(bins)
32 lgt = len(bins)
31 all = []
33 all = []
32 if not lgt:
34 if not lgt:
33 return text
35 return text
34 arg = (all, bins)
36 arg = (all, bins)
35 patch = lib.mpatch_fold(ffi.new_handle(arg), lib.cffi_get_next_item, 0, lgt)
37 patch = lib.mpatch_fold(ffi.new_handle(arg), lib.cffi_get_next_item, 0, lgt)
36 if not patch:
38 if not patch:
37 raise mpatchError(b"cannot decode chunk")
39 raise mpatchError(b"cannot decode chunk")
38 outlen = lib.mpatch_calcsize(len(text), patch)
40 outlen = lib.mpatch_calcsize(len(text), patch)
39 if outlen < 0:
41 if outlen < 0:
40 lib.mpatch_lfree(patch)
42 lib.mpatch_lfree(patch)
41 raise mpatchError(b"inconsistency detected")
43 raise mpatchError(b"inconsistency detected")
42 buf = ffi.new(b"char[]", outlen)
44 buf = ffi.new(b"char[]", outlen)
43 if lib.mpatch_apply(buf, text, len(text), patch) < 0:
45 if lib.mpatch_apply(buf, text, len(text), patch) < 0:
44 lib.mpatch_lfree(patch)
46 lib.mpatch_lfree(patch)
45 raise mpatchError(b"error applying patches")
47 raise mpatchError(b"error applying patches")
46 res = ffi.buffer(buf, outlen)[:]
48 res = ffi.buffer(buf, outlen)[:]
47 lib.mpatch_lfree(patch)
49 lib.mpatch_lfree(patch)
48 return res
50 return res
@@ -1,134 +1,143 b''
1 # mpatch.py - Python implementation of mpatch.c
1 # mpatch.py - Python implementation of mpatch.c
2 #
2 #
3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
3 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8
8
9 import io
9 import io
10 import struct
10 import struct
11
11
12 from typing import (
13 List,
14 Tuple,
15 )
16
12
17
13 stringio = io.BytesIO
18 stringio = io.BytesIO
14
19
15
20
16 class mpatchError(Exception):
21 class mpatchError(Exception):
17 """error raised when a delta cannot be decoded"""
22 """error raised when a delta cannot be decoded"""
18
23
19
24
20 # This attempts to apply a series of patches in time proportional to
25 # This attempts to apply a series of patches in time proportional to
21 # the total size of the patches, rather than patches * len(text). This
26 # the total size of the patches, rather than patches * len(text). This
22 # means rather than shuffling strings around, we shuffle around
27 # means rather than shuffling strings around, we shuffle around
23 # pointers to fragments with fragment lists.
28 # pointers to fragments with fragment lists.
24 #
29 #
25 # When the fragment lists get too long, we collapse them. To do this
30 # When the fragment lists get too long, we collapse them. To do this
26 # efficiently, we do all our operations inside a buffer created by
31 # efficiently, we do all our operations inside a buffer created by
27 # mmap and simply use memmove. This avoids creating a bunch of large
32 # mmap and simply use memmove. This avoids creating a bunch of large
28 # temporary string buffers.
33 # temporary string buffers.
29
34
30
35
31 def _pull(dst, src, l): # pull l bytes from src
36 def _pull(
37 dst: List[Tuple[int, int]], src: List[Tuple[int, int]], l: int
38 ) -> None: # pull l bytes from src
32 while l:
39 while l:
33 f = src.pop()
40 f = src.pop()
34 if f[0] > l: # do we need to split?
41 if f[0] > l: # do we need to split?
35 src.append((f[0] - l, f[1] + l))
42 src.append((f[0] - l, f[1] + l))
36 dst.append((l, f[1]))
43 dst.append((l, f[1]))
37 return
44 return
38 dst.append(f)
45 dst.append(f)
39 l -= f[0]
46 l -= f[0]
40
47
41
48
42 def _move(m, dest, src, count):
49 def _move(m: stringio, dest: int, src: int, count: int) -> None:
43 """move count bytes from src to dest
50 """move count bytes from src to dest
44
51
45 The file pointer is left at the end of dest.
52 The file pointer is left at the end of dest.
46 """
53 """
47 m.seek(src)
54 m.seek(src)
48 buf = m.read(count)
55 buf = m.read(count)
49 m.seek(dest)
56 m.seek(dest)
50 m.write(buf)
57 m.write(buf)
51
58
52
59
53 def _collect(m, buf, list):
60 def _collect(
61 m: stringio, buf: int, list: List[Tuple[int, int]]
62 ) -> Tuple[int, int]:
54 start = buf
63 start = buf
55 for l, p in reversed(list):
64 for l, p in reversed(list):
56 _move(m, buf, p, l)
65 _move(m, buf, p, l)
57 buf += l
66 buf += l
58 return (buf - start, start)
67 return (buf - start, start)
59
68
60
69
61 def patches(a, bins):
70 def patches(a: bytes, bins: List[bytes]) -> bytes:
62 if not bins:
71 if not bins:
63 return a
72 return a
64
73
65 plens = [len(x) for x in bins]
74 plens = [len(x) for x in bins]
66 pl = sum(plens)
75 pl = sum(plens)
67 bl = len(a) + pl
76 bl = len(a) + pl
68 tl = bl + bl + pl # enough for the patches and two working texts
77 tl = bl + bl + pl # enough for the patches and two working texts
69 b1, b2 = 0, bl
78 b1, b2 = 0, bl
70
79
71 if not tl:
80 if not tl:
72 return a
81 return a
73
82
74 m = stringio()
83 m = stringio()
75
84
76 # load our original text
85 # load our original text
77 m.write(a)
86 m.write(a)
78 frags = [(len(a), b1)]
87 frags = [(len(a), b1)]
79
88
80 # copy all the patches into our segment so we can memmove from them
89 # copy all the patches into our segment so we can memmove from them
81 pos = b2 + bl
90 pos = b2 + bl
82 m.seek(pos)
91 m.seek(pos)
83 for p in bins:
92 for p in bins:
84 m.write(p)
93 m.write(p)
85
94
86 for plen in plens:
95 for plen in plens:
87 # if our list gets too long, execute it
96 # if our list gets too long, execute it
88 if len(frags) > 128:
97 if len(frags) > 128:
89 b2, b1 = b1, b2
98 b2, b1 = b1, b2
90 frags = [_collect(m, b1, frags)]
99 frags = [_collect(m, b1, frags)]
91
100
92 new = []
101 new = []
93 end = pos + plen
102 end = pos + plen
94 last = 0
103 last = 0
95 while pos < end:
104 while pos < end:
96 m.seek(pos)
105 m.seek(pos)
97 try:
106 try:
98 p1, p2, l = struct.unpack(b">lll", m.read(12))
107 p1, p2, l = struct.unpack(b">lll", m.read(12))
99 except struct.error:
108 except struct.error:
100 raise mpatchError(b"patch cannot be decoded")
109 raise mpatchError(b"patch cannot be decoded")
101 _pull(new, frags, p1 - last) # what didn't change
110 _pull(new, frags, p1 - last) # what didn't change
102 _pull([], frags, p2 - p1) # what got deleted
111 _pull([], frags, p2 - p1) # what got deleted
103 new.append((l, pos + 12)) # what got added
112 new.append((l, pos + 12)) # what got added
104 pos += l + 12
113 pos += l + 12
105 last = p2
114 last = p2
106 frags.extend(reversed(new)) # what was left at the end
115 frags.extend(reversed(new)) # what was left at the end
107
116
108 t = _collect(m, b2, frags)
117 t = _collect(m, b2, frags)
109
118
110 m.seek(t[1])
119 m.seek(t[1])
111 return m.read(t[0])
120 return m.read(t[0])
112
121
113
122
114 def patchedsize(orig, delta):
123 def patchedsize(orig: int, delta: bytes) -> int:
115 outlen, last, bin = 0, 0, 0
124 outlen, last, bin = 0, 0, 0
116 binend = len(delta)
125 binend = len(delta)
117 data = 12
126 data = 12
118
127
119 while data <= binend:
128 while data <= binend:
120 decode = delta[bin : bin + 12]
129 decode = delta[bin : bin + 12]
121 start, end, length = struct.unpack(b">lll", decode)
130 start, end, length = struct.unpack(b">lll", decode)
122 if start > end:
131 if start > end:
123 break
132 break
124 bin = data + length
133 bin = data + length
125 data = bin + 12
134 data = bin + 12
126 outlen += start - last
135 outlen += start - last
127 last = end
136 last = end
128 outlen += length
137 outlen += length
129
138
130 if bin != binend:
139 if bin != binend:
131 raise mpatchError(b"patch cannot be decoded")
140 raise mpatchError(b"patch cannot be decoded")
132
141
133 outlen += orig - last
142 outlen += orig - last
134 return outlen
143 return outlen
General Comments 0
You need to be logged in to leave comments. Login now