|
5
|
1 # Module 'cmpcache'
|
|
|
2 #
|
|
|
3 # Efficiently compare files, boolean outcome only (equal / not equal).
|
|
|
4 #
|
|
|
5 # Tricks (used in this order):
|
|
|
6 # - Use the statcache module to avoid statting files more than once
|
|
|
7 # - Files with identical type, size & mtime are assumed to be clones
|
|
|
8 # - Files with different type or size cannot be identical
|
|
|
9 # - We keep a cache of outcomes of earlier comparisons
|
|
|
10 # - We don't fork a process to run 'cmp' but read the files ourselves
|
|
|
11
|
|
776
|
12 import os
|
|
29
|
13 from stat import *
|
|
5
|
14 import statcache
|
|
|
15
|
|
|
16
|
|
|
17 # The cache.
|
|
|
18 #
|
|
|
19 cache = {}
|
|
|
20
|
|
|
21
|
|
|
22 # Compare two files, use the cache if possible.
|
|
776
|
23 # May raise os.error if a stat or open of either fails.
|
|
5
|
24 #
|
|
|
25 def cmp(f1, f2):
|
|
|
26 # Return 1 for identical files, 0 for different.
|
|
|
27 # Raise exceptions if either file could not be statted, read, etc.
|
|
|
28 s1, s2 = sig(statcache.stat(f1)), sig(statcache.stat(f2))
|
|
29
|
29 if not S_ISREG(s1[0]) or not S_ISREG(s2[0]):
|
|
5
|
30 # Either is a not a plain file -- always report as different
|
|
|
31 return 0
|
|
559
|
32 if s1 == s2:
|
|
5
|
33 # type, size & mtime match -- report same
|
|
|
34 return 1
|
|
|
35 if s1[:2] <> s2[:2]: # Types or sizes differ, don't bother
|
|
|
36 # types or sizes differ -- report different
|
|
|
37 return 0
|
|
|
38 # same type and size -- look in the cache
|
|
|
39 key = f1 + ' ' + f2
|
|
|
40 if cache.has_key(key):
|
|
|
41 cs1, cs2, outcome = cache[key]
|
|
|
42 # cache hit
|
|
559
|
43 if s1 == cs1 and s2 == cs2:
|
|
5
|
44 # cached signatures match
|
|
|
45 return outcome
|
|
|
46 # stale cached signature(s)
|
|
|
47 # really compare
|
|
|
48 outcome = do_cmp(f1, f2)
|
|
|
49 cache[key] = s1, s2, outcome
|
|
|
50 return outcome
|
|
|
51
|
|
|
52 # Return signature (i.e., type, size, mtime) from raw stat data.
|
|
|
53 #
|
|
|
54 def sig(st):
|
|
29
|
55 return S_IFMT(st[ST_MODE]), st[ST_SIZE], st[ST_MTIME]
|
|
5
|
56
|
|
|
57 # Compare two files, really.
|
|
|
58 #
|
|
|
59 def do_cmp(f1, f2):
|
|
|
60 #print ' cmp', f1, f2 # XXX remove when debugged
|
|
1965
|
61 bufsize = 8*1024 # Could be tuned
|
|
5
|
62 fp1 = open(f1, 'r')
|
|
|
63 fp2 = open(f2, 'r')
|
|
|
64 while 1:
|
|
|
65 b1 = fp1.read(bufsize)
|
|
|
66 b2 = fp2.read(bufsize)
|
|
|
67 if b1 <> b2: return 0
|
|
|
68 if not b1: return 1
|