You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

109 lines
3.8 KiB

  1. import os
  2. import re
  3. import subprocess
  4. import sys
  5. from functools import partial
  6. from threading import Thread
  7. from gooey.gui import events
  8. from gooey.gui.pubsub import pub
  9. from gooey.gui.util.casting import safe_float
  10. from gooey.gui.util.taskkill import taskkill
  11. from gooey.util.functional import unit, bind
  12. class ProcessController(object):
  13. def __init__(self, progress_regex, progress_expr, hide_progress_msg,
  14. encoding, shell=True):
  15. self._process = None
  16. self.progress_regex = progress_regex
  17. self.progress_expr = progress_expr
  18. self.hide_progress_msg = hide_progress_msg
  19. self.encoding = encoding
  20. self.wasForcefullyStopped = False
  21. self.shell_execution = shell
  22. def was_success(self):
  23. self._process.communicate()
  24. return self._process.returncode == 0
  25. def poll(self):
  26. if not self._process:
  27. raise Exception('Not started!')
  28. self._process.poll()
  29. def stop(self):
  30. if self.running():
  31. self.wasForcefullyStopped = True
  32. taskkill(self._process.pid)
  33. def running(self):
  34. return self._process and self.poll() is None
  35. def run(self, command):
  36. self.wasForcefullyStopped = False
  37. env = os.environ.copy()
  38. env["GOOEY"] = "1"
  39. env["PYTHONIOENCODING"] = self.encoding
  40. try:
  41. self._process = subprocess.Popen(
  42. command.encode(sys.getfilesystemencoding()),
  43. bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
  44. stderr=subprocess.STDOUT, shell=self.shell_execution, env=env)
  45. except:
  46. self._process = subprocess.Popen(
  47. command,
  48. bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
  49. stderr = subprocess.STDOUT, shell = self.shell_execution, env=env)
  50. t = Thread(target=self._forward_stdout, args=(self._process,))
  51. t.start()
  52. def _forward_stdout(self, process):
  53. '''
  54. Reads the stdout of `process` and forwards lines and progress
  55. to any interested subscribers
  56. '''
  57. while True:
  58. line = process.stdout.readline()
  59. if not line:
  60. break
  61. _progress = self._extract_progress(line)
  62. pub.send_message(events.PROGRESS_UPDATE, progress=_progress)
  63. if _progress is None or self.hide_progress_msg is False:
  64. pub.send_message(events.CONSOLE_UPDATE,
  65. msg=line.decode(self.encoding))
  66. pub.send_message(events.EXECUTION_COMPLETE)
  67. def _extract_progress(self, text):
  68. '''
  69. Finds progress information in the text using the
  70. user-supplied regex and calculation instructions
  71. '''
  72. # monad-ish dispatch to avoid the if/else soup
  73. find = partial(re.search, string=text.strip().decode(self.encoding))
  74. regex = unit(self.progress_regex)
  75. match = bind(regex, find)
  76. result = bind(match, self._calculate_progress)
  77. return result
  78. def _calculate_progress(self, match):
  79. '''
  80. Calculates the final progress value found by the regex
  81. '''
  82. if not self.progress_expr:
  83. return safe_float(match.group(1))
  84. else:
  85. return self._eval_progress(match)
  86. def _eval_progress(self, match):
  87. '''
  88. Runs the user-supplied progress calculation rule
  89. '''
  90. _locals = {k: safe_float(v) for k, v in match.groupdict().items()}
  91. if "x" not in _locals:
  92. _locals["x"] = [safe_float(x) for x in match.groups()]
  93. try:
  94. return int(eval(self.progress_expr, {}, _locals))
  95. except:
  96. return None