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.

163 lines
5.5 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
Elapsed / Remaining Time on Progress Bar parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c author Jack McKew <jackmckew2@gmail.com> 1594344614 +1000 committer Jack McKew <jackmckew2@gmail.com> 1594346638 +1000 Elapsed / Remaining Time on Progress Bar parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c author Fizban <jackmckew2@gmail.com> 1593182730 +1000 committer Fizban <jackmckew2@gmail.com> 1593246420 +1000 parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c author Fizban <jackmckew2@gmail.com> 1593182730 +1000 committer Fizban <jackmckew2@gmail.com> 1593246359 +1000 Elapsed / Remaining Time on Progress Bar Elapsed / Remaining Time on Progress Bar Time Remaining Text for Progress Time remaining somewhat working Time format working Add tests and pythn 2.7 compat Incase python 2 import perf counter in func Fix flickering text and align Remove transactUI Add new decorators Update time remaining notes in README Amend tests with new arguments Remove unused import Separate into time module Move to dictionary structure options Explicitly show time text Amend tests for separate module Integration test - missing on complete tests Remove old code Elapsed / Remaining Time on Progress Bar parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c author Fizban <jackmckew2@gmail.com> 1593182730 +1000 committer Fizban <jackmckew2@gmail.com> 1593246420 +1000 parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c author Fizban <jackmckew2@gmail.com> 1593182730 +1000 committer Fizban <jackmckew2@gmail.com> 1593246359 +1000 Elapsed / Remaining Time on Progress Bar Elapsed / Remaining Time on Progress Bar Time Remaining Text for Progress Time remaining somewhat working Time format working Add tests and pythn 2.7 compat Incase python 2 import perf counter in func Fix flickering text and align Remove transactUI Add new decorators Update time remaining notes in README Amend tests with new arguments Remove unused import Separate into time module Move to dictionary structure options Explicitly show time text Amend tests for separate module Remove old code Pass testdata as dict instead of kwargs Merge dictionaries for defaults Test almost working Delete settings.json Revert "Test almost working" This reverts commit f17d50681cae664719f67a7e8cc0b1feaf1ac4c7. Back to working state Remove unhelpful test Update docs Working without updated test Add tests for time remaining text Elapsed / Remaining Time on Progress Bar parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c author Jack McKew <jackmckew2@gmail.com> 1594344614 +1000 committer Jack McKew <jackmckew2@gmail.com> 1594346638 +1000 Elapsed / Remaining Time on Progress Bar parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c author Fizban <jackmckew2@gmail.com> 1593182730 +1000 committer Fizban <jackmckew2@gmail.com> 1593246420 +1000 parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c author Fizban <jackmckew2@gmail.com> 1593182730 +1000 committer Fizban <jackmckew2@gmail.com> 1593246359 +1000 Elapsed / Remaining Time on Progress Bar Elapsed / Remaining Time on Progress Bar Time Remaining Text for Progress Time remaining somewhat working Time format working Add tests and pythn 2.7 compat Incase python 2 import perf counter in func Fix flickering text and align Remove transactUI Add new decorators Update time remaining notes in README Amend tests with new arguments Remove unused import Separate into time module Move to dictionary structure options Explicitly show time text Amend tests for separate module Integration test - missing on complete tests Remove old code Elapsed / Remaining Time on Progress Bar parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c author Fizban <jackmckew2@gmail.com> 1593182730 +1000 committer Fizban <jackmckew2@gmail.com> 1593246420 +1000 parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c author Fizban <jackmckew2@gmail.com> 1593182730 +1000 committer Fizban <jackmckew2@gmail.com> 1593246359 +1000 Elapsed / Remaining Time on Progress Bar Elapsed / Remaining Time on Progress Bar Time Remaining Text for Progress Time remaining somewhat working Time format working Add tests and pythn 2.7 compat Incase python 2 import perf counter in func Fix flickering text and align Remove transactUI Add new decorators Update time remaining notes in README Amend tests with new arguments Remove unused import Separate into time module Move to dictionary structure options Explicitly show time text Amend tests for separate module Remove old code Pass testdata as dict instead of kwargs Merge dictionaries for defaults Test almost working Delete settings.json Revert "Test almost working" This reverts commit f17d50681cae664719f67a7e8cc0b1feaf1ac4c7. Back to working state Remove unhelpful test Update docs Working without updated test Add tests for time remaining text Remove artifact from squashing Amend artifacts Handle no progress and tidy up footer label
4 years ago
2 years ago
  1. import os
  2. import re
  3. import signal
  4. import subprocess
  5. import sys
  6. from functools import partial
  7. from threading import Thread
  8. import psutil # type: ignore
  9. from gooey.gui import events
  10. from gooey.gui.pubsub import pub
  11. from gooey.gui.util.casting import safe_float
  12. from gooey.util.functional import unit, bind
  13. from gooey.python_bindings.types import GooeyParams
  14. try:
  15. import _winapi
  16. creationflag = subprocess.CREATE_NEW_PROCESS_GROUP
  17. except ModuleNotFoundError:
  18. # default Popen creation flag
  19. creationflag = 0
  20. class ProcessController(object):
  21. @classmethod
  22. def of(cls, params: GooeyParams):
  23. return cls(
  24. params.get('progress_regex'),
  25. params.get('progress_expr'),
  26. params.get('hide_progress_msg'),
  27. params.get('encoding'),
  28. params.get('requires_shell'),
  29. params.get('shutdown_signal', signal.SIGTERM)
  30. )
  31. def __init__(self, progress_regex, progress_expr, hide_progress_msg,
  32. encoding, shell=True, shutdown_signal=signal.SIGTERM, testmode=False):
  33. self._process = None
  34. self.progress_regex = progress_regex
  35. self.progress_expr = progress_expr
  36. self.hide_progress_msg = hide_progress_msg
  37. self.encoding = encoding
  38. self.wasForcefullyStopped = False
  39. self.shell_execution = shell
  40. self.shutdown_signal = shutdown_signal
  41. self.testMode = testmode
  42. def was_success(self):
  43. self._process.communicate()
  44. return self._process.returncode == 0
  45. def poll(self):
  46. if not self._process:
  47. raise Exception('Not started!')
  48. return self._process.poll()
  49. def stop(self):
  50. """
  51. Sends a signal of the user's choosing (default SIGTERM) to
  52. the child process.
  53. """
  54. if self.running():
  55. self.wasForcefullyStopped = True
  56. self.send_shutdown_signal()
  57. def send_shutdown_signal(self):
  58. self._send_signal(self.shutdown_signal)
  59. def _send_signal(self, sig):
  60. parent = psutil.Process(self._process.pid)
  61. for child in parent.children(recursive=True):
  62. child.send_signal(sig)
  63. parent.send_signal(sig)
  64. def running(self):
  65. return self._process and self.poll() is None
  66. def run(self, command):
  67. """
  68. Kicks off the user's code in a subprocess.
  69. Implementation Note: CREATE_NEW_SUBPROCESS is required to have signals behave sanely
  70. on windows. See the signal_support module for full background.
  71. """
  72. self.wasForcefullyStopped = False
  73. env = os.environ.copy()
  74. env["GOOEY"] = "1"
  75. env["PYTHONIOENCODING"] = self.encoding
  76. # TODO: why is this try/catch here..?
  77. try:
  78. self._process = subprocess.Popen(
  79. command.encode(sys.getfilesystemencoding()),
  80. stdout=subprocess.PIPE, stdin=subprocess.PIPE,
  81. stderr=subprocess.STDOUT, shell=self.shell_execution, env=env,
  82. creationflags=creationflag)
  83. except:
  84. self._process = subprocess.Popen(
  85. command,
  86. stdout=subprocess.PIPE, stdin=subprocess.PIPE,
  87. stderr = subprocess.STDOUT, shell = self.shell_execution, env=env,
  88. creationflags=creationflag
  89. )
  90. # the message pump depends on the wx instance being initiated and its
  91. # mainloop running (to dispatch pubsub messages). This makes testing difficult
  92. # so we only spin up the thread when we're not testing.
  93. if not self.testMode:
  94. t = Thread(target=self._forward_stdout, args=(self._process,))
  95. t.start()
  96. def _forward_stdout(self, process):
  97. '''
  98. Reads the stdout of `process` and forwards lines and progress
  99. to any interested subscribers
  100. '''
  101. while True:
  102. line = process.stdout.readline()
  103. if not line:
  104. break
  105. _progress = self._extract_progress(line)
  106. pub.send_message(events.PROGRESS_UPDATE, progress=_progress)
  107. if _progress is None or self.hide_progress_msg is False:
  108. pub.send_message(events.CONSOLE_UPDATE,
  109. msg=line.decode(self.encoding))
  110. pub.send_message(events.EXECUTION_COMPLETE)
  111. def _extract_progress(self, text):
  112. '''
  113. Finds progress information in the text using the
  114. user-supplied regex and calculation instructions
  115. '''
  116. # monad-ish dispatch to avoid the if/else soup
  117. find = partial(re.search, string=text.strip().decode(self.encoding))
  118. regex = unit(self.progress_regex)
  119. match = bind(regex, find)
  120. result = bind(match, self._calculate_progress)
  121. return result
  122. def _calculate_progress(self, match):
  123. '''
  124. Calculates the final progress value found by the regex
  125. '''
  126. if not self.progress_expr:
  127. return safe_float(match.group(1))
  128. else:
  129. return self._eval_progress(match)
  130. def _eval_progress(self, match):
  131. '''
  132. Runs the user-supplied progress calculation rule
  133. '''
  134. _locals = {k: safe_float(v) for k, v in match.groupdict().items()}
  135. if "x" not in _locals:
  136. _locals["x"] = [safe_float(x) for x in match.groups()]
  137. try:
  138. return int(eval(self.progress_expr, {}, _locals))
  139. except:
  140. return None