Stream Deno upgrade output and add GUI thread
Add streaming progress support to Deno upgrades and hook it into the GUI. upgrade_deno() now accepts an optional progress_callback and uses subprocess.Popen to read stdout line-by-line (line-buffered, utf-8, errors replaced), collecting output and invoking the callback as lines arrive. Added DenoUpdateThread (QThread) with finished/progress/error signals and replaced the previous background thread usage in the updater UI with this QThread. GUI slots strip ANSI escapes, truncate long messages, and update status text; buttons are re-enabled on finish/error. Improved logging and error handling for upgrade failures.
This commit is contained in:
+32
-10
@@ -683,10 +683,13 @@ def compare_deno_versions(current: str, latest: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def upgrade_deno() -> tuple[bool, str]:
|
||||
def upgrade_deno(progress_callback=None) -> tuple[bool, str]:
|
||||
"""
|
||||
Upgrade Deno to the latest version using 'deno upgrade' command.
|
||||
|
||||
Args:
|
||||
progress_callback: Optional function to call with output lines for progress tracking
|
||||
|
||||
Returns:
|
||||
tuple: (success: bool, output: str) - Success status and command output
|
||||
"""
|
||||
@@ -700,23 +703,42 @@ def upgrade_deno() -> tuple[bool, str]:
|
||||
|
||||
logger.info(f"Upgrading Deno using: {deno_path}")
|
||||
|
||||
# Run deno upgrade command
|
||||
result = subprocess.run(
|
||||
# Run deno upgrade command with output capturing
|
||||
process = subprocess.Popen(
|
||||
[str(deno_path), "upgrade"],
|
||||
capture_output=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
timeout=300, # 5 minutes timeout
|
||||
creationflags=SUBPROCESS_CREATIONFLAGS
|
||||
creationflags=SUBPROCESS_CREATIONFLAGS,
|
||||
bufsize=1, # Line buffered
|
||||
encoding='utf-8',
|
||||
errors='replace'
|
||||
)
|
||||
|
||||
output = result.stdout + result.stderr
|
||||
full_output = []
|
||||
|
||||
if result.returncode == 0:
|
||||
# Read output line by line as it is generated
|
||||
while True:
|
||||
line = process.stdout.readline()
|
||||
if not line and process.poll() is not None:
|
||||
break
|
||||
|
||||
if line:
|
||||
line_str = line.strip()
|
||||
if line_str:
|
||||
full_output.append(line_str)
|
||||
logger.debug(f"Deno upgrade output: {line_str}")
|
||||
if progress_callback:
|
||||
progress_callback(line_str)
|
||||
|
||||
return_code = process.poll()
|
||||
output = "\n".join(full_output)
|
||||
|
||||
if return_code == 0:
|
||||
logger.info("Deno upgrade successful")
|
||||
logger.debug(f"Upgrade output: {output}")
|
||||
return True, output
|
||||
else:
|
||||
logger.error(f"Deno upgrade failed with code {result.returncode}")
|
||||
logger.error(f"Deno upgrade failed with code {return_code}")
|
||||
logger.error(f"Output: {output}")
|
||||
return False, output
|
||||
|
||||
|
||||
@@ -183,6 +183,21 @@ class DenoCheckThread(QThread):
|
||||
self.error.emit(str(e))
|
||||
|
||||
|
||||
class DenoUpdateThread(QThread):
|
||||
finished = Signal(bool, str)
|
||||
progress = Signal(str)
|
||||
error = Signal(str)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
success, output = upgrade_deno(progress_callback=self.progress.emit)
|
||||
self.finished.emit(success, output)
|
||||
except Exception as e:
|
||||
logger.exception(f"Error updating Deno: {e}")
|
||||
self.error.emit(str(e))
|
||||
|
||||
|
||||
|
||||
class UpdaterTabWidget(QWidget):
|
||||
"""Widget for the Updater tab in Custom Options dialog."""
|
||||
|
||||
@@ -930,24 +945,41 @@ class UpdaterTabWidget(QWidget):
|
||||
"background-color: #2a2d36; border-radius: 4px; margin: 5px 0;"
|
||||
)
|
||||
|
||||
# Run update in background thread
|
||||
def update_thread():
|
||||
try:
|
||||
success, output = upgrade_deno()
|
||||
# Use QThread for updates with progress reporting
|
||||
self.deno_update_thread = DenoUpdateThread()
|
||||
self.deno_update_thread.progress.connect(self._on_deno_update_progress)
|
||||
self.deno_update_thread.finished.connect(self._on_deno_update_finished)
|
||||
self.deno_update_thread.error.connect(self._on_deno_update_error)
|
||||
self.deno_update_thread.start()
|
||||
|
||||
# Update UI in main thread
|
||||
self.deno_check_button.setEnabled(True)
|
||||
self.deno_update_button.setEnabled(True)
|
||||
self._handle_deno_update_result(success, output)
|
||||
@Slot(str)
|
||||
def _on_deno_update_progress(self, message: str) -> None:
|
||||
"""Handle Deno update progress messages."""
|
||||
# Clean up message for display
|
||||
# Strip ANSI escape codes (colors)
|
||||
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
||||
display_msg = ansi_escape.sub('', message).strip()
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"Error updating Deno: {e}")
|
||||
self.deno_check_button.setEnabled(True)
|
||||
self.deno_update_button.setEnabled(True)
|
||||
self._handle_deno_update_result(False, str(e))
|
||||
if not display_msg:
|
||||
return
|
||||
|
||||
thread = threading.Thread(target=update_thread, daemon=True)
|
||||
thread.start()
|
||||
# If message is too long, truncate it
|
||||
if len(display_msg) > 70:
|
||||
display_msg = display_msg[:67] + "..."
|
||||
|
||||
self.deno_status_label.setText(display_msg)
|
||||
|
||||
@Slot(bool, str)
|
||||
def _on_deno_update_finished(self, success: bool, output: str) -> None:
|
||||
self.deno_check_button.setEnabled(True)
|
||||
self.deno_update_button.setEnabled(True)
|
||||
self._handle_deno_update_result(success, output)
|
||||
|
||||
@Slot(str)
|
||||
def _on_deno_update_error(self, error: str) -> None:
|
||||
self.deno_check_button.setEnabled(True)
|
||||
self.deno_update_button.setEnabled(True)
|
||||
self._handle_deno_update_result(False, error)
|
||||
|
||||
def _handle_deno_update_result(self, success: bool, output: str) -> None:
|
||||
"""Handle Deno update completion."""
|
||||
|
||||
Reference in New Issue
Block a user