add a class DebExtractor for guiding feature extraction
[~helmut/debian-dedup.git] / dedup / debpkg.py
1 from debian import deb822
2
3 from dedup.arreader import ArReader
4 from dedup.hashing import hash_file
5
6 def process_control(control_contents):
7     """Parses the contents of a control file from a control.tar of a Debian
8     package and returns a dictionary containing the fields relevant to dedup.
9     @type control_contents: bytes
10     @rtype: {str: object}
11     """
12     control = deb822.Packages(control_contents)
13     package = control["package"]
14     try:
15         source = control["source"].split()[0]
16     except KeyError:
17         source = package
18     version = control["version"]
19     architecture = control["architecture"]
20     # deb822 currently returns :any dependencies raw. see #670679
21     depends = set(dep[0]["name"].split(u':', 1)[0]
22                   for dep in control.relations.get("depends", ())
23                   if len(dep) == 1)
24     return dict(package=package, source=source, version=version,
25                 architecture=architecture, depends=depends)
26
27 class MultiHash(object):
28     def __init__(self, *hashes):
29         self.hashes = hashes
30
31     def update(self, data):
32         for hasher in self.hashes:
33             hasher.update(data)
34
35 def get_tar_hashes(tar, hash_functions):
36     """Given a TarFile read all regular files and compute all of the given hash
37     functions on each file.
38     @type tar: tarfile.TarFile
39     @param hash_functions: a sequence of parameter-less functions each creating a
40             new hashlib-like object
41     @rtype: gen((str, int, {str: str}}
42     @returns: an iterable of (filename, filesize, hashes) tuples where
43             hashes is a dict mapping hash function names to hash values
44     """
45
46     for elem in tar:
47         if not elem.isreg(): # excludes hard links as well
48             continue
49         hasher = MultiHash(*[func() for func in hash_functions])
50         hasher = hash_file(hasher, tar.extractfile(elem))
51         hashes = {}
52         for hashobj in hasher.hashes:
53             hashvalue = hashobj.hexdigest()
54             if hashvalue:
55                 hashes[hashobj.name] = hashvalue
56         yield (elem.name, elem.size, hashes)
57
58 class DebExtractor(object):
59     "Base class for extracting desired features from a Debian package."
60
61     def process(self, filelike):
62         """Process a Debian package.
63         @param filelike: is a file-like object containing the contents of the
64                          Debian packge and can be read once without seeks.
65         """
66         af = ArReader(filelike)
67         af.read_magic()
68         while True:
69             try:
70                 name = af.read_entry()
71             except EOFError:
72                 break
73             else:
74                 self.handle_ar_member(name, af)
75         self.handle_ar_end()
76
77     def handle_ar_member(self, name, filelike):
78         """Handle an ar archive member of the Debian package.
79         @type name: bytes
80         @param name: is the name of the member
81         @param filelike: is a file-like object containing the contents of the
82                          member and can be read once without seeks.
83         """
84
85     def handle_ar_end(self):
86         "Handle the end of the ar archive of the Debian package."