[Bf-extensions-cvs] [24bd94f] temp-blend-utils: Add subprocess operator min-in class

Campbell Barton noreply at git.blender.org
Thu Jan 28 06:09:49 CET 2016


Commit: 24bd94fa755f903bab94f89920bf3ddb2905120c
Author: Campbell Barton
Date:   Thu Jan 28 15:55:38 2016 +1100
Branches: temp-blend-utils
https://developer.blender.org/rBA24bd94fa755f903bab94f89920bf3ddb2905120c

Add subprocess operator min-in class

===================================================================

A	io_blend_utils/utils/subprocess_helper.py

===================================================================

diff --git a/io_blend_utils/utils/subprocess_helper.py b/io_blend_utils/utils/subprocess_helper.py
new file mode 100644
index 0000000..efe62fb
--- /dev/null
+++ b/io_blend_utils/utils/subprocess_helper.py
@@ -0,0 +1,155 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+"""
+Defines an operator mix-in to use for non-blocking command line access.
+"""
+
+from pipe_non_blocking import (
+        pipe_non_blocking_set,
+        pipe_non_blocking_is_error_blocking,
+        PortableBlockingIOError,
+        )
+
+
+class SubprocessHelper:
+    """
+    Mix-in class for operators to run commands in a non-blocking way.
+
+    This uses a modal operator to manage an external process.
+
+    This class defines:
+
+        process: The running process object.
+
+    Subclass must define:
+
+        command: List of arguments to pass to subprocess.Popen
+    """
+
+    @staticmethod
+    def _non_blocking_readlines(f, chunk=64):
+        """
+        Iterate over lines, yielding b'' when nothings left
+        or when new data is not yet available.
+        """
+
+        fd = f.fileno()
+        pipe_non_blocking_set(fd)
+
+        blocks = []
+
+        while True:
+            try:
+                data = os.read(fd, chunk)
+                if not data:
+                    # case were reading finishes with no trailing newline
+                    yield b''.join(blocks)
+                    blocks.clear()
+            except PortableBlockingIOError as ex:
+                if not pipe_non_blocking_is_error_blocking(ex):
+                    raise ex
+
+                yield b''
+                continue
+
+            n = data.find(b'\n')
+            if n != -1:
+                yield b''.join(blocks) + data[:n + 1]
+                data = data[n + 1:]
+                blocks.clear()
+            blocks.append(data)
+
+    def _report_output(self):
+        stdout_line_iter, stderr_line_iter = self._buffer_iter
+        for line_iter, report_type in (
+                (stdout_line_iter, {'INFO'}),
+                (stderr_line_iter, {'WARNING'})
+                ):
+            while True:
+                line = next(line_iter).rstrip()  # rstrip all, to include \r on windows
+                if not line:
+                    break
+                self.report(report_type, line.decode(encoding='utf-8', errors='surrogateescape'))
+
+    def _wm_enter(self, context):
+        wm = context.window_manager
+        window = context.window
+
+        self._timer = wm.event_timer_add(0.1, context.window)
+        context.window.cursor_set('WAIT')
+
+    def _wm_exit(self, context):
+        wm = context.window_manager
+        window = context.window
+
+        wm.event_timer_remove(self._timer)
+        window.cursor_set('DEFAULT')
+
+
+    def modal(self, context, event):
+        wm = context.window_manager
+        p = self._process
+
+        if event.type == 'ESC':
+            self.cancel(context)
+            self.report({'INFO'}, "Operation aborted by user.")
+            return {'CANCELLED'}
+
+        elif event.type == 'TIMER':
+            if p.poll() is not None:
+                self._report_output()
+                self._wm_exit(context)
+                return {'FINISHED'}
+
+            self._report_output()
+
+        return {'PASS_THROUGH'}
+
+    def execute(self, context):
+        import subprocess
+        try:
+            p = subprocess.Popen(
+                    self.command,
+                    stdout=subprocess.PIPE,
+                    stderr=subprocess.PIPE,
+                    )
+        except FileNotFoundError as ex:
+            # Command not found
+            self.report({'ERROR'}, str(ex))
+            return {'CANCELLED'}
+
+        self._process = p
+        self._buffer_iter = (
+                iter(self._non_blocking_readlines(p.stdout)),
+                iter(self._non_blocking_readlines(p.stderr)),
+                )
+
+        wm = context.window_manager
+        wm.modal_handler_add(self)
+
+        self._wm_enter(context)
+
+        return {'RUNNING_MODAL'}
+
+    def cancel(self, context):
+        self._wm_exit(context)
+        self._process.kill()
+



More information about the Bf-extensions-cvs mailing list