[Reproducible-commits] [diffoscope] 02/06: Use lazy extraction instead of explicit bracketing

Jérémy Bobbio lunar at moszumanska.debian.org
Sat Dec 5 23:18:58 UTC 2015


This is an automated email from the git hooks/post-receive script.

lunar pushed a commit to branch master
in repository diffoscope.

commit 303aee9455687fdc34b17de52cc315def4ba2a2f
Author: Jérémy Bobbio <lunar at debian.org>
Date:   Fri Sep 25 14:19:32 2015 +0000

    Use lazy extraction instead of explicit bracketing
    
    Previously, code requiring access to file content had to be explicitly
    bracketed using get_content() for files to be extracted and then deleted.
    Such construction is problematic for parallel processing as a file might be
    processed be multiple operations currently (e.g. multiple files being extracted
    from a unique archive at the same time).
    
    We thus removes the get_content() context and @needs_content decorator to
    prefer lazy path initialization: actual content will be made available through
    the path property. The extraction will happen then if necessary.
    
    The extracted file should normally be deleted when Python garbage collector
    reclaims the object. As a safety net, we still have a global registry of all
    temporary files and directories and remove them on exit.
---
 diffoscope/__init__.py               | 30 ++++++++++++++
 diffoscope/__main__.py               |  4 +-
 diffoscope/comparators/__init__.py   | 25 ++++++------
 diffoscope/comparators/binary.py     | 79 ++++++++++++++----------------------
 diffoscope/comparators/bzip2.py      |  3 +-
 diffoscope/comparators/cbfs.py       | 68 +++++++++++++++----------------
 diffoscope/comparators/cpio.py       |  3 +-
 diffoscope/comparators/deb.py        |  5 +--
 diffoscope/comparators/debian.py     | 61 ++++++++++++----------------
 diffoscope/comparators/device.py     | 26 ++++++++----
 diffoscope/comparators/dex.py        |  3 +-
 diffoscope/comparators/directory.py  | 28 +++++--------
 diffoscope/comparators/elf.py        |  4 +-
 diffoscope/comparators/fonts.py      |  3 +-
 diffoscope/comparators/fsimage.py    |  3 +-
 diffoscope/comparators/gettext.py    |  3 +-
 diffoscope/comparators/gzip.py       |  2 -
 diffoscope/comparators/haskell.py    | 44 ++++++++++----------
 diffoscope/comparators/image.py      |  3 +-
 diffoscope/comparators/iso9660.py    |  3 +-
 diffoscope/comparators/java.py       |  3 +-
 diffoscope/comparators/libarchive.py |  6 +--
 diffoscope/comparators/mono.py       |  3 +-
 diffoscope/comparators/pdf.py        |  3 +-
 diffoscope/comparators/png.py        |  3 +-
 diffoscope/comparators/ppu.py        | 40 +++++++++---------
 diffoscope/comparators/rpm.py        |  8 ++--
 diffoscope/comparators/sqlite.py     |  3 +-
 diffoscope/comparators/squashfs.py   |  9 ++--
 diffoscope/comparators/symlink.py    | 26 ++++++++----
 diffoscope/comparators/tar.py        |  4 +-
 diffoscope/comparators/text.py       |  6 +--
 diffoscope/comparators/utils.py      | 51 +++++++++++------------
 diffoscope/comparators/xz.py         |  3 +-
 diffoscope/comparators/zip.py        |  3 +-
 35 files changed, 274 insertions(+), 297 deletions(-)

diff --git a/diffoscope/__init__.py b/diffoscope/__init__.py
index b5f36d8..4fb99bb 100644
--- a/diffoscope/__init__.py
+++ b/diffoscope/__init__.py
@@ -21,6 +21,8 @@ from functools import wraps
 import logging
 from distutils.spawn import find_executable
 import os
+import shutil
+import tempfile
 
 VERSION = "42"
 
@@ -115,4 +117,32 @@ def set_locale():
     os.environ['TZ'] = 'UTC'
 
 
+temp_files = []
+temp_dirs = []
 
+
+def get_named_temporary_file(*args, **kwargs):
+    f = tempfile.NamedTemporaryFile(*args, **kwargs)
+    temp_files.append(f.name)
+    return f
+
+
+def get_temporary_directory(*args, **kwargs):
+    d = tempfile.TemporaryDirectory(*args, **kwargs)
+    temp_dirs.append(d)
+    return d
+
+
+def clean_all_temp_files():
+    for temp_file in temp_files:
+        try:
+            os.unlink(temp_file)
+        except FileNotFoundError:
+            pass
+        except:
+            logger.exception('Unable to delete %s', temp_file)
+    for temp_dir in temp_dirs:
+        try:
+            temp_dir.cleanup()
+        except:
+            logger.exception('Unable to delete %s', temp_dir)
diff --git a/diffoscope/__main__.py b/diffoscope/__main__.py
index ecb784c..c027b54 100644
--- a/diffoscope/__main__.py
+++ b/diffoscope/__main__.py
@@ -30,7 +30,7 @@ try:
     import tlsh
 except ImportError:
     tlsh = None
-from diffoscope import logger, VERSION, set_locale
+from diffoscope import logger, VERSION, set_locale, clean_all_temp_files
 import diffoscope.comparators
 from diffoscope.config import Config
 from diffoscope.presenters.html import output_html
@@ -153,6 +153,8 @@ def main(args=None):
             import pdb
             pdb.post_mortem()
         sys.exit(2)
+    finally:
+        clean_all_temp_files()
 
 if __name__ == '__main__':
     main()
diff --git a/diffoscope/comparators/__init__.py b/diffoscope/comparators/__init__.py
index 86080f9..61d673f 100644
--- a/diffoscope/comparators/__init__.py
+++ b/diffoscope/comparators/__init__.py
@@ -94,19 +94,18 @@ def compare_root_paths(path1, path2):
 
 def compare_files(file1, file2, source=None):
     logger.debug('compare files %s and %s', file1, file2)
-    with file1.get_content(), file2.get_content():
-        if file1.has_same_content_as(file2):
-            logger.debug('same content, skipping')
-            return None
-        specialize(file1)
-        specialize(file2)
-        if isinstance(file1, NonExistingFile):
-            file1.other_file = file2
-        elif isinstance(file2, NonExistingFile):
-            file2.other_file = file1
-        elif file1.__class__.__name__ != file2.__class__.__name__:
-            return file1.compare_bytes(file2, source)
-        return file1.compare(file2, source)
+    if file1.has_same_content_as(file2):
+        logger.debug('same content, skipping')
+        return None
+    specialize(file1)
+    specialize(file2)
+    if isinstance(file1, NonExistingFile):
+        file1.other_file = file2
+    elif isinstance(file2, NonExistingFile):
+        file2.other_file = file1
+    elif file1.__class__.__name__ != file2.__class__.__name__:
+        return file1.compare_bytes(file2, source)
+    return file1.compare(file2, source)
 
 def compare_commented_files(file1, file2, comment=None, source=None):
     difference = compare_files(file1, file2, source=source)
diff --git a/diffoscope/comparators/binary.py b/diffoscope/comparators/binary.py
index 33d27fe..b2a40cb 100644
--- a/diffoscope/comparators/binary.py
+++ b/diffoscope/comparators/binary.py
@@ -71,14 +71,6 @@ def compare_binary_files(file1, file2, source=None):
 
 SMALL_FILE_THRESHOLD = 65536 # 64 kiB
 
-# decorator for functions which needs to access the file content
-# (and so requires a path to be set)
-def needs_content(original_method):
-    @wraps(original_method)
-    def wrapper(self, other, *args, **kwargs):
-        with self.get_content(), other.get_content():
-            return original_method(self, other, *args, **kwargs)
-    return wrapper
 
 class File(object, metaclass=ABCMeta):
     if hasattr(magic, 'open'): # use Magic-file-extensions from file
@@ -109,12 +101,22 @@ class File(object, metaclass=ABCMeta):
             return self._mimedb_encoding.from_file(path).decode('utf-8')
 
     def __repr__(self):
-        return '<%s %s %s>' % (self.__class__, self.name, self.path)
+        return '<%s %s>' % (self.__class__, self.name)
 
-    # Path should only be used when accessing the file content (through get_content())
+    # This should return a path that allows to access the file content
     @property
+    @abstractmethod
     def path(self):
-        return self._path
+        raise NotImplemented
+
+    # Remove any temporary data associated with the file. The function
+    # should be idempotent and work during the destructor. It does nothing by
+    # default.
+    def cleanup(self):
+        pass
+
+    def __del__(self):
+        self.cleanup()
 
     # This might be different from path and is used to do file extension matching
     @property
@@ -124,33 +126,26 @@ class File(object, metaclass=ABCMeta):
     @property
     def magic_file_type(self):
         if not hasattr(self, '_magic_file_type'):
-            with self.get_content():
-                self._magic_file_type = File.guess_file_type(self.path)
+            self._magic_file_type = File.guess_file_type(self.path)
         return self._magic_file_type
 
     if tlsh:
         @property
         def fuzzy_hash(self):
             if not hasattr(self, '_fuzzy_hash'):
-                with self.get_content():
-                    # tlsh is not meaningful with files smaller than 512 bytes
-                    if os.stat(self.path).st_size >= 512:
-                        h = tlsh.Tlsh()
-                        with open(self.path, 'rb') as f:
-                            for buf in iter(lambda: f.read(32768), b''):
-                                h.update(buf)
-                        h.final()
-                        self._fuzzy_hash = h.hexdigest()
-                    else:
-                        self._fuzzy_hash = None
+                # tlsh is not meaningful with files smaller than 512 bytes
+                if os.stat(self.path).st_size >= 512:
+                    h = tlsh.Tlsh()
+                    with open(self.path, 'rb') as f:
+                        for buf in iter(lambda: f.read(32768), b''):
+                            h.update(buf)
+                    h.final()
+                    self._fuzzy_hash = h.hexdigest()
+                else:
+                    self._fuzzy_hash = None
             return self._fuzzy_hash
 
     @abstractmethod
-    @contextmanager
-    def get_content(self):
-        raise NotImplemented
-
-    @abstractmethod
     def is_directory():
         raise NotImplemented
 
@@ -162,7 +157,6 @@ class File(object, metaclass=ABCMeta):
     def is_device():
         raise NotImplemented
 
-    @needs_content
     def compare_bytes(self, other, source=None):
         return compare_binary_files(self, other, source)
 
@@ -175,7 +169,6 @@ class File(object, metaclass=ABCMeta):
         return difference
 
     @tool_required('cmp')
-    @needs_content
     def has_same_content_as(self, other):
         logger.debug('%s has_same_content %s', self, other)
         # try comparing small files directly first
@@ -190,7 +183,6 @@ class File(object, metaclass=ABCMeta):
 
 
     # To be specialized directly, or by implementing compare_details
-    @needs_content
     def compare(self, other, source=None):
         if hasattr(self, 'compare_details'):
             try:
@@ -227,17 +219,11 @@ class File(object, metaclass=ABCMeta):
 
 class FilesystemFile(File):
     def __init__(self, path):
-        self._path = None
         self._name = path
 
-    @contextmanager
-    def get_content(self):
-        if self._path is not None:
-            yield
-        else:
-            self._path = self._name
-            yield
-            self._path = None
+    @property
+    def path(self):
+        return self._name
 
     def is_directory(self):
         return not os.path.islink(self._name) and os.path.isdir(self._name)
@@ -261,11 +247,14 @@ class NonExistingFile(File):
         return False
 
     def __init__(self, path, other_file=None):
-        self._path = None
         self._name = path
         self._other_file = other_file
 
     @property
+    def path(self):
+        return '/dev/null'
+
+    @property
     def other_file(self):
         return self._other_file
 
@@ -276,12 +265,6 @@ class NonExistingFile(File):
     def has_same_content_as(self, other):
         return False
 
-    @contextmanager
-    def get_content(self):
-        self._path = '/dev/null'
-        yield
-        self._path = None
-
     def is_directory(self):
         return False
 
diff --git a/diffoscope/comparators/bzip2.py b/diffoscope/comparators/bzip2.py
index 806e329..aef9c46 100644
--- a/diffoscope/comparators/bzip2.py
+++ b/diffoscope/comparators/bzip2.py
@@ -21,7 +21,7 @@ import os.path
 import re
 import subprocess
 import diffoscope.comparators
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Archive, get_compressed_content_name, NO_COMMENT
 from diffoscope import logger, tool_required
 
@@ -62,7 +62,6 @@ class Bzip2File(File):
     def recognizes(file):
         return Bzip2File.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         with Bzip2Container(self).open() as my_container, \
              Bzip2Container(other).open() as other_container:
diff --git a/diffoscope/comparators/cbfs.py b/diffoscope/comparators/cbfs.py
index bf708dd..00efa5a 100644
--- a/diffoscope/comparators/cbfs.py
+++ b/diffoscope/comparators/cbfs.py
@@ -25,7 +25,7 @@ import subprocess
 import stat
 import struct
 from diffoscope import logger, tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Archive, Command
 from diffoscope.difference import Difference
 
@@ -94,40 +94,38 @@ def is_header_valid(buf, size, offset=0):
 class CbfsFile(File):
     @staticmethod
     def recognizes(file):
-        with file.get_content():
-            size = os.stat(file.path).st_size
-            if size < CBFS_HEADER_SIZE:
-                return False
-            with open(file.path, 'rb') as f:
-                # pick at the latest byte as it should contain the relative offset of the header
-                f.seek(-4, io.SEEK_END)
-                # <pgeorgi> given the hardware we support so far, it looks like
-                #           that field is now bound to be little endian
-                #   -- #coreboot, 2015-10-14
-                rel_offset = struct.unpack('<i', f.read(4))[0]
-                if rel_offset < 0 and -rel_offset > CBFS_HEADER_SIZE and -rel_offset < size:
-                    f.seek(rel_offset, io.SEEK_END)
-                    logger.debug('looking for header at offset: %x', f.tell())
-                    if is_header_valid(f.read(CBFS_HEADER_SIZE), size):
-                        return True
-                    elif not file.name.endswith('.rom'):
-                        return False
-                    else:
-                        logger.debug('CBFS relative offset seems wrong, scanning whole image')
-                f.seek(0, io.SEEK_SET)
-                offset = 0
-                buf = f.read(CBFS_HEADER_SIZE)
-                while len(buf) >= CBFS_HEADER_SIZE:
-                    if is_header_valid(buf, size, offset):
-                        return True
-                    if len(buf) - offset <= CBFS_HEADER_SIZE:
-                        buf = f.read(32768)
-                        offset = 0
-                    else:
-                        offset += 1
-                return False
-
-    @needs_content
+        size = os.stat(file.path).st_size
+        if size < CBFS_HEADER_SIZE:
+            return False
+        with open(file.path, 'rb') as f:
+            # pick at the latest byte as it should contain the relative offset of the header
+            f.seek(-4, io.SEEK_END)
+            # <pgeorgi> given the hardware we support so far, it looks like
+            #           that field is now bound to be little endian
+            #   -- #coreboot, 2015-10-14
+            rel_offset = struct.unpack('<i', f.read(4))[0]
+            if rel_offset < 0 and -rel_offset > CBFS_HEADER_SIZE and -rel_offset < size:
+                f.seek(rel_offset, io.SEEK_END)
+                logger.debug('looking for header at offset: %x', f.tell())
+                if is_header_valid(f.read(CBFS_HEADER_SIZE), size):
+                    return True
+                elif not file.name.endswith('.rom'):
+                    return False
+                else:
+                    logger.debug('CBFS relative offset seems wrong, scanning whole image')
+            f.seek(0, io.SEEK_SET)
+            offset = 0
+            buf = f.read(CBFS_HEADER_SIZE)
+            while len(buf) >= CBFS_HEADER_SIZE:
+                if is_header_valid(buf, size, offset):
+                    return True
+                if len(buf) - offset <= CBFS_HEADER_SIZE:
+                    buf = f.read(32768)
+                    offset = 0
+                else:
+                    offset += 1
+            return False
+
     def compare_details(self, other, source=None):
         differences = []
         differences.append(Difference.from_command(CbfsListing, self.path, other.path))
diff --git a/diffoscope/comparators/cpio.py b/diffoscope/comparators/cpio.py
index d11ba78..4f2f76b 100644
--- a/diffoscope/comparators/cpio.py
+++ b/diffoscope/comparators/cpio.py
@@ -20,7 +20,7 @@
 
 import re
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.libarchive import LibarchiveContainer
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
@@ -39,7 +39,6 @@ class CpioFile(File):
     def recognizes(file):
         return CpioFile.RE_FILE_TYPE.search(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         differences.append(Difference.from_command(
diff --git a/diffoscope/comparators/deb.py b/diffoscope/comparators/deb.py
index 9968c05..ba1e529 100644
--- a/diffoscope/comparators/deb.py
+++ b/diffoscope/comparators/deb.py
@@ -21,7 +21,7 @@ import re
 import os.path
 from diffoscope import logger
 from diffoscope.difference import Difference
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.libarchive import LibarchiveContainer
 from diffoscope.comparators.utils import \
     Archive, ArchiveMember, get_ar_content
@@ -49,7 +49,6 @@ class DebFile(File):
     def set_files_with_same_content_in_data(self, files):
         self._files_with_same_content_in_data = files
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         my_content = get_ar_content(self.path)
@@ -81,7 +80,6 @@ class Md5sumsFile(File):
                 d[path] = md5sum
         return d
 
-    @needs_content
     def compare(self, other, source=None):
         if other.path is None:
             return None
@@ -122,7 +120,6 @@ class DebDataTarFile(File):
                file.container.source.name.startswith('data.tar.') and \
                isinstance(file.container.source.container.source, DebFile)
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         ignore_files = self.container.source.container.source.files_with_same_content_in_data
diff --git a/diffoscope/comparators/debian.py b/diffoscope/comparators/debian.py
index a361e5d..d6a54ba 100644
--- a/diffoscope/comparators/debian.py
+++ b/diffoscope/comparators/debian.py
@@ -25,7 +25,7 @@ import re
 from debian.deb822 import Dsc
 from diffoscope.changes import Changes
 import diffoscope.comparators
-from diffoscope.comparators.binary import File, NonExistingFile, needs_content
+from diffoscope.comparators.binary import File, NonExistingFile
 from diffoscope.comparators.utils import Container, NO_COMMENT
 from diffoscope.config import Config
 from diffoscope.difference import Difference
@@ -53,15 +53,9 @@ class DebControlMember(File):
     def name(self):
         return self._name
 
-    @contextmanager
-    def get_content(self):
-        if self._path is not None:
-            yield
-        else:
-            with self.container.source.get_content():
-                self._path = os.path.join(os.path.dirname(self.container.source.path), self.name)
-                yield
-                self._path = None
+    @property
+    def path(self):
+        return os.path.join(os.path.dirname(self.container.source.path), self.name)
 
     def is_directory(self):
         return False
@@ -109,7 +103,6 @@ class DebControlFile(File):
     def deb822(self):
         return self._deb822
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
 
@@ -142,14 +135,13 @@ class DotChangesFile(DebControlFile):
     def recognizes(file):
         if not DotChangesFile.RE_FILE_EXTENSION.search(file.name):
             return False
-        with file.get_content():
-            changes = Changes(filename=file.path)
-            try:
-                changes.validate(check_signature=False)
-            except FileNotFoundError:
-                return False
-            file._deb822 = changes
-            return True
+        changes = Changes(filename=file.path)
+        try:
+            changes.validate(check_signature=False)
+        except FileNotFoundError:
+            return False
+        file._deb822 = changes
+        return True
 
 class DotDscFile(DebControlFile):
     RE_FILE_EXTENSION = re.compile(r'\.dsc$')
@@ -158,19 +150,18 @@ class DotDscFile(DebControlFile):
     def recognizes(file):
         if not DotDscFile.RE_FILE_EXTENSION.search(file.name):
             return False
-        with file.get_content():
-            with open(file.path, 'rb') as f:
-                dsc = Dsc(f)
-                for d in dsc.get('Files'):
-                    md5 = hashlib.md5()
-                    # XXX: this will not work for containers
-                    in_dsc_path = os.path.join(os.path.dirname(file.path), d['Name'])
-                    if not os.path.exists(in_dsc_path):
-                        return False
-                    with open(in_dsc_path, 'rb') as f:
-                        for buf in iter(partial(f.read, 32768), b''):
-                            md5.update(buf)
-                    if md5.hexdigest() != d['md5sum']:
-                        return False
-                file._deb822 = dsc
-            return True
+        with open(file.path, 'rb') as f:
+            dsc = Dsc(f)
+            for d in dsc.get('Files'):
+                md5 = hashlib.md5()
+                # XXX: this will not work for containers
+                in_dsc_path = os.path.join(os.path.dirname(file.path), d['Name'])
+                if not os.path.exists(in_dsc_path):
+                    return False
+                with open(in_dsc_path, 'rb') as f:
+                    for buf in iter(partial(f.read, 32768), b''):
+                        md5.update(buf)
+                if md5.hexdigest() != d['md5sum']:
+                    return False
+            file._deb822 = dsc
+        return True
diff --git a/diffoscope/comparators/device.py b/diffoscope/comparators/device.py
index cd9aaf0..6416f50 100644
--- a/diffoscope/comparators/device.py
+++ b/diffoscope/comparators/device.py
@@ -20,10 +20,10 @@
 from contextlib import contextmanager
 import os
 import tempfile
-from diffoscope.comparators.binary import File, FilesystemFile, needs_content
+from diffoscope.comparators.binary import File, FilesystemFile
 from diffoscope.comparators.utils import format_device
 from diffoscope.difference import Difference
-from diffoscope import logger
+from diffoscope import logger, get_named_temporary_file
 
 
 class Device(File):
@@ -39,16 +39,24 @@ class Device(File):
     def has_same_content_as(self, other):
         return self.get_device() == other.get_device()
 
-    @contextmanager
-    def get_content(self):
-        with tempfile.NamedTemporaryFile(mode='w+', suffix='diffoscope') as f:
+    def create_placeholder(self):
+        with get_named_temporary_file(mode='w+', suffix='diffoscope', delete=False) as f:
             f.write(format_device(*self.get_device()))
             f.flush()
-            self._path = f.name
-            yield
-            self._path = None
+            return f.name
+
+    @property
+    def path(self):
+        if not hasattr(self, '_placeholder'):
+            self._placeholder = self.create_placeholder()
+        return self._placeholder
+
+    def cleanup(self):
+        if hasattr(self, '_placeholder'):
+            os.remove(self._placeholder)
+            del self._placeholder
+        super().cleanup()
 
-    @needs_content
     def compare(self, other, source=None):
         with open(self.path) as my_content, \
              open(other.path) as other_content:
diff --git a/diffoscope/comparators/dex.py b/diffoscope/comparators/dex.py
index 003adaa..3e96324 100644
--- a/diffoscope/comparators/dex.py
+++ b/diffoscope/comparators/dex.py
@@ -21,7 +21,7 @@ import re
 import os.path
 import subprocess
 from diffoscope import logger, tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Archive, get_compressed_content_name
 from diffoscope.difference import Difference
 
@@ -59,7 +59,6 @@ class DexFile(File):
     def recognizes(file):
         return DexFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         with DexContainer(self).open() as my_container, \
              DexContainer(other).open() as other_container:
diff --git a/diffoscope/comparators/directory.py b/diffoscope/comparators/directory.py
index 2141572..07d7d9a 100644
--- a/diffoscope/comparators/directory.py
+++ b/diffoscope/comparators/directory.py
@@ -113,10 +113,6 @@ class FilesystemDirectory(object):
     def name(self):
         return self._path
 
-    @contextmanager
-    def get_content(self):
-        yield
-
     def is_directory(self):
         return True
 
@@ -140,15 +136,14 @@ class FilesystemDirectory(object):
             for name in sorted(set(my_names).intersection(other_names)):
                 my_file = my_container.get_member(name)
                 other_file = other_container.get_member(name)
-                with my_file.get_content(), other_file.get_content():
-                    inner_difference = diffoscope.comparators.compare_files(
-                                           my_file, other_file, source=name)
-                    meta_differences = compare_meta(my_file.name, other_file.name)
-                    if meta_differences and not inner_difference:
-                        inner_difference = Difference(None, my_file.path, other_file.path)
-                    if inner_difference:
-                        inner_difference.add_details(meta_differences)
-                        differences.append(inner_difference)
+                inner_difference = diffoscope.comparators.compare_files(
+                                       my_file, other_file, source=name)
+                meta_differences = compare_meta(my_file.name, other_file.name)
+                if meta_differences and not inner_difference:
+                    inner_difference = Difference(None, my_file.path, other_file.path)
+                if inner_difference:
+                    inner_difference.add_details(meta_differences)
+                    differences.append(inner_difference)
         if not differences:
             return None
         difference = Difference(None, self.path, other.path, source)
@@ -159,10 +154,9 @@ class FilesystemDirectory(object):
 class DirectoryContainer(Container):
     @contextmanager
     def open(self):
-        with self.source.get_content():
-            self._path = self.source.path.rstrip('/') or '/'
-            yield self
-            self._path = None
+        self._path = self.source.path.rstrip('/') or '/'
+        yield self
+        self._path = None
 
     def get_member_names(self):
         names = []
diff --git a/diffoscope/comparators/elf.py b/diffoscope/comparators/elf.py
index 38cf665..30f6953 100644
--- a/diffoscope/comparators/elf.py
+++ b/diffoscope/comparators/elf.py
@@ -20,7 +20,7 @@
 import os.path
 import re
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import get_ar_content, Command
 from diffoscope.difference import Difference
 
@@ -90,7 +90,6 @@ class ElfFile(File):
     def recognizes(file):
         return ElfFile.RE_FILE_TYE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         return _compare_elf_data(self.path, other.path)
 
@@ -102,7 +101,6 @@ class StaticLibFile(File):
     def recognizes(file):
         return StaticLibFile.RE_FILE_TYPE.search(file.magic_file_type) and StaticLibFile.RE_FILE_EXTENSION.search(file.name)
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         # look up differences in metadata
diff --git a/diffoscope/comparators/fonts.py b/diffoscope/comparators/fonts.py
index 859c924..187655a 100644
--- a/diffoscope/comparators/fonts.py
+++ b/diffoscope/comparators/fonts.py
@@ -19,7 +19,7 @@
 
 import re
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
 
@@ -40,6 +40,5 @@ class TtfFile(File):
     def recognizes(file):
         return TtfFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         return [Difference.from_command(Showttf, self.path, other.path)]
diff --git a/diffoscope/comparators/fsimage.py b/diffoscope/comparators/fsimage.py
index ca76a3a..edd6ab4 100644
--- a/diffoscope/comparators/fsimage.py
+++ b/diffoscope/comparators/fsimage.py
@@ -24,7 +24,7 @@ try:
 except ImportError:
     guestfs = None
 from diffoscope import logger
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Archive, get_compressed_content_name
 from diffoscope.difference import Difference
 
@@ -77,7 +77,6 @@ class FsImageFile(File):
     def recognizes(file):
         return FsImageFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         with FsImageContainer(self).open() as my_container, \
diff --git a/diffoscope/comparators/gettext.py b/diffoscope/comparators/gettext.py
index 6ded32d..b739900 100644
--- a/diffoscope/comparators/gettext.py
+++ b/diffoscope/comparators/gettext.py
@@ -20,7 +20,7 @@
 from io import BytesIO
 import re
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
 from diffoscope import logger
@@ -63,6 +63,5 @@ class MoFile(File):
     def recognizes(file):
         return MoFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         return [Difference.from_command(Msgunfmt, self.path, other.path)]
diff --git a/diffoscope/comparators/gzip.py b/diffoscope/comparators/gzip.py
index f151d48..481b028 100644
--- a/diffoscope/comparators/gzip.py
+++ b/diffoscope/comparators/gzip.py
@@ -22,7 +22,6 @@ import subprocess
 import os.path
 import diffoscope.comparators
 from diffoscope import logger, tool_required
-from diffoscope.comparators.binary import needs_content
 from diffoscope.comparators.utils import Archive, get_compressed_content_name, NO_COMMENT
 from diffoscope.difference import Difference
 
@@ -63,7 +62,6 @@ class GzipFile(object):
     def recognizes(file):
         return GzipFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         differences.append(Difference.from_text(
diff --git a/diffoscope/comparators/haskell.py b/diffoscope/comparators/haskell.py
index 6f696fa..7bd4025 100644
--- a/diffoscope/comparators/haskell.py
+++ b/diffoscope/comparators/haskell.py
@@ -22,7 +22,7 @@ import platform
 import struct
 import subprocess
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
 from diffoscope import logger
@@ -60,29 +60,27 @@ class HiFile(File):
         if HiFile.hi_version is None:
             return False
 
-        with file.get_content():
-            with open(file.path, 'rb') as fp:
-                # read magic
-                buf = fp.read(4)
-                if buf != HI_MAGIC:
-                    logger.debug('Haskell interface magic mismatch. Found %r instead of %r or %r', buf, HI_MAGIC_32, HI_MAGIC_64)
-                    return False
-                # skip some old descriptor thingy that has varying size
-                if buf == HI_MAGIC_32:
-                    fp.read(4)
-                elif buf == HI_MAGIC_64:
-                    fp.read(8)
-                # skip way_descr
+        with open(file.path, 'rb') as fp:
+            # read magic
+            buf = fp.read(4)
+            if buf != HI_MAGIC:
+                logger.debug('Haskell interface magic mismatch. Found %r instead of %r or %r', buf, HI_MAGIC_32, HI_MAGIC_64)
+                return False
+            # skip some old descriptor thingy that has varying size
+            if buf == HI_MAGIC_32:
                 fp.read(4)
-                # now read version
-                buf = fp.read(16)
-                version_found = ''.join(map(chr, struct.unpack_from('=3IB', buf)))
-                if version_found != HiFile.hi_version:
-                    logger.debug('Haskell version mismatch. Found %s instead of %s.',
-                                 version_found, HiFile.hi_version)
-                    return False
-                return True
+            elif buf == HI_MAGIC_64:
+                fp.read(8)
+            # skip way_descr
+            fp.read(4)
+            # now read version
+            buf = fp.read(16)
+            version_found = ''.join(map(chr, struct.unpack_from('=3IB', buf)))
+            if version_found != HiFile.hi_version:
+                logger.debug('Haskell version mismatch. Found %s instead of %s.',
+                             version_found, HiFile.hi_version)
+                return False
+            return True
 
-    @needs_content
     def compare_details(self, other, source=None):
         return [Difference.from_command(ShowIface, self.path, other.path)]
diff --git a/diffoscope/comparators/image.py b/diffoscope/comparators/image.py
index c44c2f1..d781b55 100644
--- a/diffoscope/comparators/image.py
+++ b/diffoscope/comparators/image.py
@@ -22,7 +22,7 @@ import re
 from diffoscope import tool_required
 from diffoscope.difference import Difference
 from diffoscope.comparators.utils import Command
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 
 re_ansi_escapes = re.compile(r'\x1b[^m]*m')
 
@@ -47,6 +47,5 @@ class ImageFile(File):
     def recognizes(file):
         return ImageFile.RE_FILE_TYPE.search(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         return [Difference.from_command(Img2Txt, self.path, other.path)]
diff --git a/diffoscope/comparators/iso9660.py b/diffoscope/comparators/iso9660.py
index cf21ccf..3eca892 100644
--- a/diffoscope/comparators/iso9660.py
+++ b/diffoscope/comparators/iso9660.py
@@ -20,7 +20,7 @@
 import re
 import subprocess
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.libarchive import LibarchiveContainer
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
@@ -68,7 +68,6 @@ class Iso9660File(File):
     def recognizes(file):
         return Iso9660File.RE_FILE_TYPE.search(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         differences.append(Difference.from_command(ISO9660PVD, self.path, other.path))
diff --git a/diffoscope/comparators/java.py b/diffoscope/comparators/java.py
index abf9972..f8b6b5e 100644
--- a/diffoscope/comparators/java.py
+++ b/diffoscope/comparators/java.py
@@ -21,7 +21,7 @@
 import os.path
 import re
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
 
@@ -48,6 +48,5 @@ class ClassFile(File):
     def recognizes(file):
         return ClassFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         return [Difference.from_command(Javap, self.path, other.path)]
diff --git a/diffoscope/comparators/libarchive.py b/diffoscope/comparators/libarchive.py
index 7d93d44..9a79312 100644
--- a/diffoscope/comparators/libarchive.py
+++ b/diffoscope/comparators/libarchive.py
@@ -63,9 +63,9 @@ class LibarchiveDirectory(Directory, LibarchiveMember):
     def has_same_content_as(self, other):
         return False
 
-    @contextmanager
-    def get_content(self):
-        yield
+    @property
+    def path(self):
+        raise NotImplementedError('LibarchiveDirectory is not meant to be extracted.')
 
     def is_directory(self):
         return True
diff --git a/diffoscope/comparators/mono.py b/diffoscope/comparators/mono.py
index c563390..cfbf87d 100644
--- a/diffoscope/comparators/mono.py
+++ b/diffoscope/comparators/mono.py
@@ -20,7 +20,7 @@
 
 import re
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
 
@@ -38,6 +38,5 @@ class MonoExeFile(File):
     def recognizes(file):
         return MonoExeFile.RE_FILE_TYPE.search(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         return [Difference.from_command(Pedump, self.path, other.path)]
diff --git a/diffoscope/comparators/pdf.py b/diffoscope/comparators/pdf.py
index 2f15733..d105c07 100644
--- a/diffoscope/comparators/pdf.py
+++ b/diffoscope/comparators/pdf.py
@@ -19,7 +19,7 @@
 
 import re
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
 
@@ -46,7 +46,6 @@ class PdfFile(File):
     def recognizes(file):
         return PdfFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         differences.append(Difference.from_command(Pdftotext, self.path, other.path))
diff --git a/diffoscope/comparators/png.py b/diffoscope/comparators/png.py
index 845441e..0328e33 100644
--- a/diffoscope/comparators/png.py
+++ b/diffoscope/comparators/png.py
@@ -20,7 +20,7 @@
 from functools import partial
 import re
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
 
@@ -43,6 +43,5 @@ class PngFile(File):
     def recognizes(file):
         return PngFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         return [Difference.from_command(Sng, self.path, other.path, source='sng')]
diff --git a/diffoscope/comparators/ppu.py b/diffoscope/comparators/ppu.py
index ca3aad7..4fdbb32 100644
--- a/diffoscope/comparators/ppu.py
+++ b/diffoscope/comparators/ppu.py
@@ -23,7 +23,7 @@ import os
 import re
 import subprocess
 from diffoscope import tool_required, logger
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
 
@@ -58,28 +58,26 @@ class PpuFile(File):
     def recognizes(file):
         if not PpuFile.RE_FILE_EXTENSION.search(file.name):
             return False
-        with file.get_content():
-            with open(file.path, 'rb') as f:
-                magic = f.read(3)
-                if magic != b"PPU":
-                    return False
-                ppu_version = f.read(3).decode('ascii', errors='ignore')
-                if not hasattr(PpuFile, 'ppu_version'):
-                    try:
-                        subprocess.check_output(['ppudump', file.path], shell=False, stderr=subprocess.STDOUT)
-                        PpuFile.ppu_version = ppu_version
-                    except subprocess.CalledProcessError as e:
-                        error = e.output.decode('utf-8')
-                        m = re.search('Expecting PPU version ([0-9]+)', error)
-                        if not m:
-                            PpuFile.ppu_version = None
-                            logger.debug('Unable to read PPU version')
-                        PpuFile.ppu_version = m.group(1)
-                    except OSError:
+        with open(file.path, 'rb') as f:
+            magic = f.read(3)
+            if magic != b"PPU":
+                return False
+            ppu_version = f.read(3).decode('ascii', errors='ignore')
+            if not hasattr(PpuFile, 'ppu_version'):
+                try:
+                    subprocess.check_output(['ppudump', file.path], shell=False, stderr=subprocess.STDOUT)
+                    PpuFile.ppu_version = ppu_version
+                except subprocess.CalledProcessError as e:
+                    error = e.output.decode('utf-8')
+                    m = re.search('Expecting PPU version ([0-9]+)', error)
+                    if not m:
                         PpuFile.ppu_version = None
                         logger.debug('Unable to read PPU version')
-                return PpuFile.ppu_version == ppu_version
+                    PpuFile.ppu_version = m.group(1)
+                except OSError:
+                    PpuFile.ppu_version = None
+                    logger.debug('Unable to read PPU version')
+            return PpuFile.ppu_version == ppu_version
 
-    @needs_content
     def compare_details(self, other, source=None):
         return [Difference.from_command(Ppudump, self.path, other.path)]
diff --git a/diffoscope/comparators/rpm.py b/diffoscope/comparators/rpm.py
index 641dac1..7fc7427 100644
--- a/diffoscope/comparators/rpm.py
+++ b/diffoscope/comparators/rpm.py
@@ -23,10 +23,9 @@ from io import StringIO
 import os.path
 import subprocess
 import rpm
-from diffoscope import logger, tool_required
+from diffoscope import logger, tool_required, get_temporary_directory
 from diffoscope.comparators.rpm_fallback import AbstractRpmFile
-from diffoscope.comparators.binary import needs_content
-from diffoscope.comparators.utils import Archive, make_temp_directory
+from diffoscope.comparators.utils import Archive
 from diffoscope.difference import Difference
 
 def convert_header_field(io, header):
@@ -68,7 +67,7 @@ def get_rpm_header(path, ts):
 
 def compare_rpm_headers(path1, path2):
     # compare headers
-    with make_temp_directory() as rpmdb_dir:
+    with get_temporary_directory(suffix='diffoscope') as rpmdb_dir:
         rpm.addMacro("_dbpath", rpmdb_dir)
         ts = rpm.TransactionSet()
         ts.setVSFlags(-1)
@@ -103,7 +102,6 @@ class RpmContainer(Archive):
 
 
 class RpmFile(AbstractRpmFile):
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         differences.append(compare_rpm_headers(self.path, other.path))
diff --git a/diffoscope/comparators/sqlite.py b/diffoscope/comparators/sqlite.py
index b3320c8..e361dea 100644
--- a/diffoscope/comparators/sqlite.py
+++ b/diffoscope/comparators/sqlite.py
@@ -18,7 +18,7 @@
 # along with diffoscope.  If not, see <http://www.gnu.org/licenses/>.
 
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Command
 from diffoscope.difference import Difference
 
@@ -34,7 +34,6 @@ class Sqlite3Database(File):
     def recognizes(file):
         return file.magic_file_type == 'SQLite 3.x database'
 
-    @needs_content
     def compare_details(self, other, source=None):
         return [Difference.from_command(Sqlite3Dump, self.path, other.path)]
 
diff --git a/diffoscope/comparators/squashfs.py b/diffoscope/comparators/squashfs.py
index 1a3ab59..81889a2 100644
--- a/diffoscope/comparators/squashfs.py
+++ b/diffoscope/comparators/squashfs.py
@@ -23,7 +23,7 @@ import re
 import subprocess
 import stat
 from diffoscope import logger, tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.device import Device
 from diffoscope.comparators.directory import Directory
 from diffoscope.comparators.symlink import Symlink
@@ -83,9 +83,9 @@ class SquashfsDirectory(Directory, SquashfsMember):
     def has_same_content_as(self, other):
         return False
 
-    @contextmanager
-    def get_content(self):
-        yield
+    @property
+    def path(self):
+        raise NotImplementedError('SquashfsDirectory is not meant to be extracted.')
 
     def is_directory(self):
         return True
@@ -194,7 +194,6 @@ class SquashfsFile(File):
     def recognizes(file):
         return SquashfsFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         differences.append(Difference.from_command(SquashfsSuperblock, self.path, other.path))
diff --git a/diffoscope/comparators/symlink.py b/diffoscope/comparators/symlink.py
index 93dad0f..f9b4664 100644
--- a/diffoscope/comparators/symlink.py
+++ b/diffoscope/comparators/symlink.py
@@ -20,10 +20,10 @@
 from contextlib import contextmanager
 import os
 import tempfile
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import format_symlink
 from diffoscope.difference import Difference
-from diffoscope import logger
+from diffoscope import logger, get_named_temporary_file
 
 
 class Symlink(File):
@@ -35,16 +35,24 @@ class Symlink(File):
     def symlink_destination(self):
         return os.readlink(self.name)
 
-    @contextmanager
-    def get_content(self):
-        with tempfile.NamedTemporaryFile('w+', suffix='diffoscope') as f:
+    def create_placeholder(self):
+        with get_named_temporary_file('w+', suffix='diffoscope', delete=False) as f:
             f.write(format_symlink(self.symlink_destination))
             f.flush()
-            self._path = f.name
-            yield
-            self._path = None
+            return f.name
+
+    @property
+    def path(self):
+        if not hasattr(self, '_placeholder'):
+            self._placeholder = self.create_placeholder()
+        return self._placeholder
+
+    def cleanup(self):
+        if hasattr(self, '_placeholder'):
+            os.remove(self._placeholder)
+            del self._placeholder
+        super().cleanup()
 
-    @needs_content
     def compare(self, other, source=None):
         logger.debug('my_content %s', self.path)
         with open(self.path) as my_content, \
diff --git a/diffoscope/comparators/tar.py b/diffoscope/comparators/tar.py
index d2fc23d..2aba6f7 100644
--- a/diffoscope/comparators/tar.py
+++ b/diffoscope/comparators/tar.py
@@ -19,14 +19,13 @@
 
 import re
 from diffoscope.difference import Difference
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.libarchive import LibarchiveContainer
 from diffoscope.comparators.utils import Command, tool_required
 
 class TarContainer(LibarchiveContainer):
     pass
 
-
 class TarListing(Command):
     @tool_required('tar')
     def cmdline(self):
@@ -40,7 +39,6 @@ class TarFile(File):
     def recognizes(file):
         return TarFile.RE_FILE_TYPE.search(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         with TarContainer(self).open() as my_container, \
diff --git a/diffoscope/comparators/text.py b/diffoscope/comparators/text.py
index 417238a..a48a50e 100644
--- a/diffoscope/comparators/text.py
+++ b/diffoscope/comparators/text.py
@@ -19,7 +19,7 @@
 
 import codecs
 import re
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.difference import Difference
 
 
@@ -33,11 +33,9 @@ class TextFile(File):
     @property
     def encoding(self):
         if not hasattr(self, '_encoding'):
-            with self.get_content():
-                self._encoding = File.guess_encoding(self.path)
+            self._encoding = File.guess_encoding(self.path)
         return self._encoding
 
-    @needs_content
     def compare(self, other, source=None):
         my_encoding = self.encoding or 'utf-8'
         other_encoding = other.encoding or 'utf-8'
diff --git a/diffoscope/comparators/utils.py b/diffoscope/comparators/utils.py
index 767f01b..b0fc36d 100644
--- a/diffoscope/comparators/utils.py
+++ b/diffoscope/comparators/utils.py
@@ -32,16 +32,7 @@ import diffoscope.comparators
 from diffoscope.comparators.binary import File, NonExistingFile
 from diffoscope.config import Config
 from diffoscope.difference import Difference
-from diffoscope import logger, tool_required
-
-
- at contextmanager
-def make_temp_directory():
-    temp_dir = tempfile.mkdtemp(suffix='diffoscope')
-    try:
-        yield temp_dir
-    finally:
-        shutil.rmtree(temp_dir)
+from diffoscope import logger, tool_required, get_temporary_directory
 
 
 @tool_required('ar')
@@ -213,6 +204,7 @@ class ArchiveMember(File):
     def __init__(self, container, member_name):
         self._container = container
         self._name = member_name
+        self._temp_dir = None
         self._path = None
 
     @property
@@ -223,17 +215,23 @@ class ArchiveMember(File):
     def name(self):
         return self._name
 
-    @contextmanager
-    def get_content(self):
-        logger.debug('%s get_content; path %s', self, self._path)
+    @property
+    def path(self):
+        if self._path is None:
+            logger.debug('unpacking %s', self._name)
+            assert self._temp_dir is None
+            self._temp_dir = get_temporary_directory(suffix='diffoscope')
+            with self._container.open() as container:
+                self._path = container.extract(self._name, self._temp_dir.name)
+        return self._path
+
+    def cleanup(self):
         if self._path is not None:
-            yield
-        else:
-            with make_temp_directory() as temp_dir, \
-                 self._container.open() as container:
-                self._path = container.extract(self._name, temp_dir)
-                yield
-                self._path = None
+            self._path = None
+        if self._temp_dir is not None:
+            self._temp_dir.cleanup()
+            self._temp_dir = None
+        super().cleanup()
 
     def is_directory(self):
         return False
@@ -257,13 +255,12 @@ class Archive(Container, metaclass=ABCMeta):
         elif self._archive is not None:
             yield self
         else:
-            with self.source.get_content():
-                self._archive = self.open_archive(self.source.path)
-                try:
-                    yield self
-                finally:
-                    self.close_archive()
-                    self._archive = None
+            self._archive = self.open_archive(self.source.path)
+            try:
+                yield self
+            finally:
+                self.close_archive()
+                self._archive = None
 
     @property
     def archive(self):
diff --git a/diffoscope/comparators/xz.py b/diffoscope/comparators/xz.py
index 4848e65..9ff9713 100644
--- a/diffoscope/comparators/xz.py
+++ b/diffoscope/comparators/xz.py
@@ -21,7 +21,7 @@ import os.path
 import re
 import subprocess
 import diffoscope.comparators
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.utils import Archive, get_compressed_content_name, NO_COMMENT
 from diffoscope import logger, tool_required
 
@@ -62,7 +62,6 @@ class XzFile(File):
     def recognizes(file):
         return XzFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         with XzContainer(self).open() as my_container, \
              XzContainer(other).open() as other_container:
diff --git a/diffoscope/comparators/zip.py b/diffoscope/comparators/zip.py
index 801b54c..00c80ca 100644
--- a/diffoscope/comparators/zip.py
+++ b/diffoscope/comparators/zip.py
@@ -25,7 +25,7 @@ import sys
 import zipfile
 from diffoscope.difference import Difference
 from diffoscope import tool_required
-from diffoscope.comparators.binary import File, needs_content
+from diffoscope.comparators.binary import File
 from diffoscope.comparators.directory import Directory
 from diffoscope.comparators.utils import Archive, ArchiveMember, Command
 
@@ -106,7 +106,6 @@ class ZipFile(File):
     def recognizes(file):
         return ZipFile.RE_FILE_TYPE.match(file.magic_file_type)
 
-    @needs_content
     def compare_details(self, other, source=None):
         differences = []
         # look up differences in metadata

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/diffoscope.git



More information about the Reproducible-commits mailing list