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