]> arthur.barton.de Git - bup.git/blob - lib/bup/midx.py
57a478759297228622e32f102976150fe27d7525
[bup.git] / lib / bup / midx.py
1 import mmap
2 from bup import _helpers
3 from bup.helpers import *
4
5 MIDX_VERSION = 4
6
7 extract_bits = _helpers.extract_bits
8 _total_searches = 0
9 _total_steps = 0
10
11
12 class PackMidx:
13     """Wrapper which contains data from multiple index files.
14     Multiple index (.midx) files constitute a wrapper around index (.idx) files
15     and make it possible for bup to expand Git's indexing capabilities to vast
16     amounts of files.
17     """
18     def __init__(self, filename):
19         self.name = filename
20         self.force_keep = False
21         self.map = None
22         assert(filename.endswith('.midx'))
23         self.map = mmap_read(open(filename))
24         if str(self.map[0:4]) != 'MIDX':
25             log('Warning: skipping: invalid MIDX header in %r\n' % filename)
26             self.force_keep = True
27             return self._init_failed()
28         ver = struct.unpack('!I', self.map[4:8])[0]
29         if ver < MIDX_VERSION:
30             log('Warning: ignoring old-style (v%d) midx %r\n' 
31                 % (ver, filename))
32             self.force_keep = False  # old stuff is boring  
33             return self._init_failed()
34         if ver > MIDX_VERSION:
35             log('Warning: ignoring too-new (v%d) midx %r\n'
36                 % (ver, filename))
37             self.force_keep = True  # new stuff is exciting
38             return self._init_failed()
39
40         self.bits = _helpers.firstword(self.map[8:12])
41         self.entries = 2**self.bits
42         self.fanout = buffer(self.map, 12, self.entries*4)
43         self.sha_ofs = 12 + self.entries*4
44         self.nsha = nsha = self._fanget(self.entries-1)
45         self.shatable = buffer(self.map, self.sha_ofs, nsha*20)
46         self.which_ofs = self.sha_ofs + 20*nsha
47         self.whichlist = buffer(self.map, self.which_ofs, nsha*4)
48         self.idxnames = str(self.map[self.which_ofs + 4*nsha:]).split('\0')
49
50     def __del__(self):
51         self.close()
52
53     def _init_failed(self):
54         self.bits = 0
55         self.entries = 1
56         self.fanout = buffer('\0\0\0\0')
57         self.shatable = buffer('\0'*20)
58         self.idxnames = []
59
60     def _fanget(self, i):
61         start = i*4
62         s = self.fanout[start:start+4]
63         return _helpers.firstword(s)
64
65     def _get(self, i):
66         return str(self.shatable[i*20:(i+1)*20])
67
68     def _get_idx_i(self, i):
69         return struct.unpack('!I', self.whichlist[i*4:(i+1)*4])[0]
70
71     def _get_idxname(self, i):
72         return self.idxnames[self._get_idx_i(i)]
73
74     def close(self):
75         if self.map is not None:
76             self.map.close()
77             self.map = None
78
79     def exists(self, hash, want_source=False):
80         """Return nonempty if the object exists in the index files."""
81         global _total_searches, _total_steps
82         _total_searches += 1
83         want = str(hash)
84         el = extract_bits(want, self.bits)
85         if el:
86             start = self._fanget(el-1)
87             startv = el << (32-self.bits)
88         else:
89             start = 0
90             startv = 0
91         end = self._fanget(el)
92         endv = (el+1) << (32-self.bits)
93         _total_steps += 1   # lookup table is a step
94         hashv = _helpers.firstword(hash)
95         #print '(%08x) %08x %08x %08x' % (extract_bits(want, 32), startv, hashv, endv)
96         while start < end:
97             _total_steps += 1
98             #print '! %08x %08x %08x   %d - %d' % (startv, hashv, endv, start, end)
99             mid = start + (hashv-startv)*(end-start-1)/(endv-startv)
100             #print '  %08x %08x %08x   %d %d %d' % (startv, hashv, endv, start, mid, end)
101             v = self._get(mid)
102             #print '    %08x' % self._num(v)
103             if v < want:
104                 start = mid+1
105                 startv = _helpers.firstword(v)
106             elif v > want:
107                 end = mid
108                 endv = _helpers.firstword(v)
109             else: # got it!
110                 return want_source and self._get_idxname(mid) or True
111         return None
112
113     def __iter__(self):
114         for i in xrange(self._fanget(self.entries-1)):
115             yield buffer(self.shatable, i*20, 20)
116
117     def __len__(self):
118         return int(self._fanget(self.entries-1))
119
120