about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xranger/ext/rifle.py71
1 files changed, 66 insertions, 5 deletions
diff --git a/ranger/ext/rifle.py b/ranger/ext/rifle.py
index bb84b295..c3544805 100755
--- a/ranger/ext/rifle.py
+++ b/ranger/ext/rifle.py
@@ -18,9 +18,10 @@ from __future__ import (absolute_import, division, print_function)
 
 import os.path
 import re
-from subprocess import Popen, PIPE
+from subprocess import PIPE
 import sys
 
+
 __version__ = 'rifle 1.9.3'
 
 # Options and constants that a user might want to change:
@@ -70,6 +71,66 @@ except ImportError:
 
 
 try:
+    from ranger.ext.popen23 import Popen23
+except ImportError:
+    # COMPAT: Python 2 (and Python <=3.2) subprocess.Popen objects aren't
+    #         context managers. We don't care about early Python 3 but we do
+    #         want to wrap Python 2's Popen. There's no harm in always using
+    #         this Popen but it is only necessary when used with
+    #         with-statements. This can be removed once we ditch Python 2
+    #         support.
+    from contextlib import contextmanager
+    # pylint: disable=ungrouped-imports
+    from subprocess import Popen, TimeoutExpired
+
+    try:
+        from ranger import PY3
+    except ImportError:
+        from sys import version_info
+        PY3 = version_info[0] >= 3
+
+    @contextmanager
+    def Popen23(*args, **kwargs):  # pylint: disable=invalid-name
+        if PY3:
+            yield Popen(*args, **kwargs)
+            return
+        else:
+            popen2 = Popen(*args, **kwargs)
+        try:
+            yield popen2
+        finally:
+            # From Lib/subprocess.py Popen.__exit__:
+            if popen2.stdout:
+                popen2.stdout.close()
+            if popen2.stderr:
+                popen2.stderr.close()
+            try:  # Flushing a BufferedWriter may raise an error
+                if popen2.stdin:
+                    popen2.stdin.close()
+            except KeyboardInterrupt:
+                # https://bugs.python.org/issue25942
+                # In the case of a KeyboardInterrupt we assume the SIGINT
+                # was also already sent to our child processes.  We can't
+                # block indefinitely as that is not user friendly.
+                # If we have not already waited a brief amount of time in
+                # an interrupted .wait() or .communicate() call, do so here
+                # for consistency.
+                # pylint: disable=protected-access
+                if popen2._sigint_wait_secs > 0:
+                    try:
+                        # pylint: disable=no-member
+                        popen2._wait(timeout=popen2._sigint_wait_secs)
+                    except TimeoutExpired:
+                        pass
+                popen2._sigint_wait_secs = 0  # Note that this's been done.
+                # pylint: disable=lost-exception
+                return  # resume the KeyboardInterrupt
+            finally:
+                # Wait for the process to terminate, to avoid zombies.
+                popen2.wait()
+
+
+try:
     from ranger.ext.popen_forked import Popen_forked
 except ImportError:
     def Popen_forked(*args, **kwargs):  # pylint: disable=invalid-name
@@ -259,14 +320,14 @@ class Rifle(object):  # pylint: disable=too-many-instance-attributes
         self._mimetype, _ = mimetypes.guess_type(fname)
 
         if not self._mimetype:
-            with Popen(
+            with Popen23(
                 ["file", "--mime-type", "-Lb", fname], stdout=PIPE, stderr=PIPE
             ) as process:
                 mimetype, _ = process.communicate()
             self._mimetype = mimetype.decode(ENCODING).strip()
             if self._mimetype == 'application/octet-stream':
                 try:
-                    with Popen(
+                    with Popen23(
                         ["mimetype", "--output-format", "%m", fname],
                         stdout=PIPE,
                         stderr=PIPE,
@@ -445,7 +506,7 @@ class Rifle(object):  # pylint: disable=too-many-instance-attributes
                 if 'f' in flags or 't' in flags:
                     Popen_forked(cmd, env=self.hook_environment(os.environ))
                 else:
-                    with Popen(
+                    with Popen23(
                         cmd, env=self.hook_environment(os.environ)
                     ) as process:
                         process.wait()
@@ -528,7 +589,7 @@ def main():  # pylint: disable=too-many-locals
         label = options.p
 
     if options.w is not None and not options.l:
-        with Popen([options.w] + list(positional)) as process:
+        with Popen23([options.w] + list(positional)) as process:
             process.wait()
     else:
         # Start up rifle