From d14e82268e59d671140157211804f565fcbf0b7e Mon Sep 17 00:00:00 2001 From: jsbautista Date: Mon, 11 Jul 2022 00:31:27 -0500 Subject: [PATCH 01/49] Changes on check updates --- spyder/plugins/application/container.py | 102 +++++++- spyder/plugins/application/plugin.py | 18 +- spyder/plugins/application/widgets/install.py | 226 ++++++++++++++++++ spyder/plugins/application/widgets/status.py | 80 +++++++ 4 files changed, 412 insertions(+), 14 deletions(-) create mode 100644 spyder/plugins/application/widgets/install.py create mode 100644 spyder/plugins/application/widgets/status.py diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 4bc0e1955ec..df44bb6d305 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -14,6 +14,10 @@ import os import sys import glob +import subprocess +from urllib.request import urlretrieve +from tempfile import TemporaryDirectory +import threading # Third party imports from qtpy.QtCore import Qt, QThread, QTimer, Signal, Slot @@ -34,6 +38,7 @@ from spyder.widgets.dependencies import DependenciesDialog from spyder.widgets.helperwidgets import MessageCheckBox from spyder.workers.updates import WorkerUpdates +from spyder.plugins.application.widgets.status import ApplicationUpdateStatus WinUserEnvDialog = None @@ -43,16 +48,13 @@ # Localization _ = get_translation('spyder') - class ApplicationPluginMenus: DebugLogsMenu = "debug_logs_menu" - class LogsMenuSections: SpyderLogSection = "spyder_log_section" LSPLogsSection = "lsp_logs_section" - # Actions class ApplicationActions: # Help @@ -76,9 +78,27 @@ class ApplicationActions: SpyderRestart = "Restart" SpyderRestartDebug = "Restart in debug mode" +# Installation process statuses +NO_STATUS = _("No status") +DOWNLOADING_SCRIPT = _("Downloading script installer") +DOWNLOADING_INSTALLER = _("Downloading installer") +INSTALLING = _("Installing") +FINISHED = _("Installation finished") +ERRORED = _("Installation errored") +CANCELLED = _("Cancelled") class ApplicationContainer(PluginMainContainer): + # Signals + # Signal to get the current status of the installation + # str: Status string + sig_installation_status = Signal(str) + + # Signal to get the download progress + # str: Download progress + # str: Total download size + sig_download_progress = Signal(int, int) + sig_report_issue_requested = Signal() """ Signal to request reporting an issue to Github. @@ -88,10 +108,16 @@ class ApplicationContainer(PluginMainContainer): """ Signal to load a log file """ + sig_show_status_bar_widget = Signal(bool) + """ + Signal to show the widget in status bar + """ def __init__(self, name, plugin, parent=None): super().__init__(name, plugin, parent) - + self.cancelled = False + self.status=NO_STATUS + self.thread_install_update=None # Keep track of dpi message self.current_dpi = None self.dpi_messagebox = None @@ -100,8 +126,10 @@ def __init__(self, name, plugin, parent=None): # ------------------------------------------------------------------------- def setup(self): # Compute dependencies in a thread to not block the interface. + self.application_update_status = ApplicationUpdateStatus(parent=self) self.dependencies_thread = QThread(None) + # Attributes self.dialog_manager = DialogManager() self.give_updates_feedback = False @@ -235,6 +263,22 @@ def show_windows_env_variables(self): # ---- Updates # ------------------------------------------------------------------------- + def _change_update_installation_status(self, status=NO_STATUS): + """Set the installation status.""" + self.status = status + self.sig_installation_status.emit(self.status) + + def _progress_reporter(self, block_number, read_size, total_size): + progress = 0 + if total_size > 0: + progress = block_number * read_size + if self.cancelled: + raise UpdateInstallationCancelledException() + else: + self.sig_download_progress.emit(progress, total_size) + def _cancell_thread_install_update(self): + self.thread_install_update.join() + def _check_updates_ready(self): """Show results of the Spyder update checking process.""" @@ -242,7 +286,7 @@ def _check_updates_ready(self): # given. `feedback` = True is used when after startup (when using the # menu action, and gives feeback if updates are, or are not found. feedback = self.give_updates_feedback - + # Get results from worker update_available = self.worker_updates.update_available latest_release = self.worker_updates.latest_release @@ -255,8 +299,25 @@ def _check_updates_ready(self): else: url_r = ('https://github.com/spyder-ide/spyder/releases/latest/' 'download/Spyder_64bit_full.exe') - url_i = 'https://docs.spyder-ide.org/installation.html' - + url_i = 'https://docs.spyder-ide.org/installation.html' + + def _download_install(): + with TemporaryDirectory(prefix = "Spyder-") as tmpdir: + destination = os.path.join(tmpdir,'updateSpyder.exe') + download =urlretrieve(url_r, + destination, + reporthook=self._progress_reporter) + self._change_update_installation_status(status="Installing") + install = subprocess.Popen(destination, shell=True) + install.communicate() + self.application_update_status.setVisible(False) + def _thread_launcher(option): + if option.text() in ('&Yes'): + self.cancelled=False + """call a function in a simple thread, to prevent blocking""" + self.application_update_status.setVisible(True) + self.thread_install_update = threading.Thread(target = _download_install) + self.thread_install_update.start() # Define the custom QMessageBox box = MessageCheckBox(icon=QMessageBox.Information, parent=self) @@ -278,14 +339,16 @@ def _check_updates_ready(self): box.exec_() check_updates = box.is_checked() else: - if update_available: + + if True: + header = _("Spyder {} is available!

").format( latest_release) footer = _( "For more information visit our " "installation guide." ).format(url_i) - if is_anaconda(): + if not is_anaconda(): content = _( "Important note: Since you installed " "Spyder with Anaconda, please don't use " @@ -297,10 +360,17 @@ def _check_updates_ready(self): "conda install spyder={}

" ).format(latest_release) else: - content = _( - "Click this link to " - "download it.

" - ).format(url_r) + if os.name == 'nt': + box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) + content = _( + "You want to download and install the latest version of spyder?

" + ) + box.buttonClicked.connect(_thread_launcher) + else: + content = _( + "Click this link to " + "download it.

" + ).format(url_r) msg = header + content + footer box.setText(msg) box.set_check_visible(True) @@ -596,3 +666,9 @@ def show_dpi_change_message(self, dpi): # Fixes spyder-ide/spyder#17677 self.dpi_messagebox.move(int(x), int(y)) self.dpi_messagebox.adjustSize() + + + +class UpdateInstallationCancelledException(Exception): + """Update installation was cancelled.""" + pass \ No newline at end of file diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index 1be26f891ca..3558c428b68 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -43,7 +43,7 @@ class Application(SpyderPluginV2): NAME = 'application' REQUIRES = [Plugins.Console, Plugins.Preferences] OPTIONAL = [Plugins.Help, Plugins.MainMenu, Plugins.Shortcuts, - Plugins.Editor] + Plugins.Editor, Plugins.StatusBar] CONTAINER_CLASS = ApplicationContainer CONF_SECTION = 'main' CONF_FILE = False @@ -101,7 +101,19 @@ def on_editor_available(self): editor = self.get_plugin(Plugins.Editor) self.get_container().sig_load_log_file.connect(editor.load) + @on_plugin_available(plugin=Plugins.StatusBar) + def on_statusbar_available(self): + # Add status widget + statusbar = self.get_plugin(Plugins.StatusBar) + statusbar.add_status_widget(self.application_update_status) + # -------------------------- PLUGIN TEARDOWN ------------------------------ + @on_plugin_teardown(plugin=Plugins.StatusBar) + def on_statusbar_teardown(self): + # Add status widget + statusbar = self.get_plugin(Plugins.StatusBar) + statusbar.remove_status_widget(self.application_update_status.ID) + @on_plugin_teardown(plugin=Plugins.Preferences) def on_preferences_teardown(self): preferences = self.get_plugin(Plugins.Preferences) @@ -443,3 +455,7 @@ def report_action(self): def debug_logs_menu(self): return self.get_container().get_menu( ApplicationPluginMenus.DebugLogsMenu) + + @property + def application_update_status(self): + return self.get_container().application_update_status diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py new file mode 100644 index 00000000000..dfc2b8dedb1 --- /dev/null +++ b/spyder/plugins/application/widgets/install.py @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- + +# Copyright © Spyder Project Contributors +# Licensed under the terms of the MIT License +# (see spyder/__init__.py for details) + +"""Kite installation widget.""" + +# Standard library imports +import sys + +# Third-party imports +from qtpy.QtCore import QEvent, QObject, Qt, Signal +from qtpy.QtGui import QPixmap +from qtpy.QtWidgets import (QApplication, QDialog, QHBoxLayout, QMessageBox, + QLabel, QProgressBar, QPushButton, QVBoxLayout, + QWidget) + +# Local imports +from spyder.config.base import _ +from spyder.utils.image_path_manager import get_image_path +from spyder.utils.icon_manager import ima +from spyder.utils.palette import QStylePalette +from spyder.utils.stylesheet import DialogStyle + +INSTALLING = _("Installing") +FINISHED = _("Installation finished") +ERRORED = _("Installation errored") +CANCELLED = _("Cancelled") +class HoverEventFilter(QObject): + """QObject to handle event filtering.""" + # Signal to trigger on a HoverEnter event + sig_hover_enter = Signal() + # Signal to trigger on a HoverLeave event + sig_hover_leave = Signal() + + def eventFilter(self, widget, event): + """Reimplemented Qt method.""" + if event.type() == QEvent.HoverEnter: + self.sig_hover_enter.emit() + elif event.type() == QEvent.HoverLeave: + self.sig_hover_leave.emit() + + return super(HoverEventFilter, self).eventFilter(widget, event) + +class UpdateInstallation(QWidget): + """Update progress installation widget.""" + + def __init__(self, parent): + super(UpdateInstallation, self).__init__(parent) + + # Left side + action_layout = QVBoxLayout() + progress_layout = QHBoxLayout() + self._progress_widget = QWidget(self) + self._progress_widget.setFixedHeight(50) + self._progress_filter = HoverEventFilter() + self._progress_bar = QProgressBar(self) + self._progress_bar.setFixedWidth(180) + self._progress_widget.installEventFilter(self._progress_filter) + self.cancel_button = QPushButton() + self.cancel_button.setIcon(ima.icon('DialogCloseButton')) + self.cancel_button.hide() + progress_layout.addWidget(self._progress_bar, alignment=Qt.AlignLeft) + progress_layout.addWidget(self.cancel_button) + self._progress_widget.setLayout(progress_layout) + + self._progress_label = QLabel(_('Downloading')) + install_info = QLabel( + _("Dowloading the latest Spyder version
" + "which provides you with real time
" + "documentation as you code.

" + "When Kite is done installing, the Copilot will
" + "launch automatically and guide you through the
" + "rest of the setup process.")) + + button_layout = QHBoxLayout() + self.ok_button = QPushButton(_('OK')) + button_layout.addStretch() + button_layout.addWidget(self.ok_button) + button_layout.addStretch() + + action_layout.addStretch() + action_layout.addWidget(self._progress_label) + action_layout.addWidget(self._progress_widget) + action_layout.addWidget(install_info) + action_layout.addSpacing(10) + action_layout.addLayout(button_layout) + action_layout.addStretch() + + # Right side + copilot_image_source = get_image_path('kite_copilot') + + copilot_image = QPixmap(copilot_image_source) + copilot_label = QLabel() + screen = QApplication.primaryScreen() + device_pixel_ratio = screen.devicePixelRatio() + if device_pixel_ratio > 1: + copilot_image.setDevicePixelRatio(device_pixel_ratio) + copilot_label.setPixmap(copilot_image) + else: + image_height = int(copilot_image.height() * 0.4) + image_width = int(copilot_image.width() * 0.4) + copilot_label.setPixmap( + copilot_image.scaled(image_width, image_height, + Qt.KeepAspectRatio, + Qt.SmoothTransformation)) + + # Layout + general_layout = QHBoxLayout() + general_layout.addLayout(action_layout) + general_layout.addWidget(copilot_label) + + self.setLayout(general_layout) + + # Signals + self._progress_filter.sig_hover_enter.connect( + lambda: self.cancel_button.show()) + self._progress_filter.sig_hover_leave.connect( + lambda: self.cancel_button.hide()) + + def update_installation_status(self, status): + """Update installation status (downloading, installing, finished).""" + self._progress_label.setText(status) + if status == INSTALLING: + self._progress_bar.setRange(0, 0) + + def update_installation_progress(self, current_value, total): + """Update installation progress bar.""" + self._progress_bar.setMaximum(total) + self._progress_bar.setValue(current_value) + + +class UpdateInstallerDialog(QDialog): + """Kite installer.""" + + def __init__(self, parent, installation_thread): + super(UpdateInstallerDialog, self).__init__(parent) + self.setStyleSheet( + f"background-color: {QStylePalette.COLOR_BACKGROUND_2}") + self.setWindowFlags(Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) + self._parent = parent + self._installation_thread = installation_thread + self._installation_widget = UpdateInstallation(self) + + # Layout + installer_layout = QVBoxLayout() + installer_layout.addWidget(self._installation_widget) + + self.setLayout(installer_layout) + + # Signals + self._installation_thread.sig_download_progress.connect( + self._installation_widget.update_installation_progress) + self._installation_thread.sig_installation_status.connect( + self._installation_widget.update_installation_status) + + + #self._installation_thread.sig_error_msg.connect(self._handle_error_msg) + + self._installation_widget.ok_button.clicked.connect( + self.close_installer) + self._installation_widget.cancel_button.clicked.connect( + self.cancel_install) + + # Show integration widget + self.setup() + + def _handle_error_msg(self, msg): + """Handle error message with an error dialog.""" + '''QMessageBox.critical( + self._parent, + _('Kite installation error'), + _("An error ocurred while installing Kite!

" + "Please try to " + "install it manually or " + "contact Kite for help") + .format(kite_url=KITE_SPYDER_URL, kite_contact=KITE_CONTACT_URL))''' + self.accept() + + def setup(self,installation=False): + """Setup visibility of widgets.""" + self._installation_widget.setVisible(False) + self.adjustSize() + + + def cancel_install(self): + """Cancel the installation in progress.""" + reply = QMessageBox.critical( + self._parent, 'Spyder', + _('Do you really want to cancel Update installation?'), + QMessageBox.Yes, QMessageBox.No) + if reply == QMessageBox.Yes: + self._installation_thread.cancelled = True + self._installation_thread._cancell_thread_install_update() + self.setup() + self.accept() + return True + return False + + + + def close_installer(self): + """Close the installation dialog.""" + if (self._installation_thread.status == ERRORED + or self._installation_thread.status == FINISHED + or self._installation_thread.status == CANCELLED): + self.setup() + self.accept() + else: + self.hide() + + def reject(self): + """Reimplement Qt method.""" + on_installation_widget = self._installation_widget.isVisible() + if on_installation_widget: + self.close_installer() + else: + super(UpdateInstallerDialog, self).reject() + +if __name__ == "__main__": + from spyder.utils.qthelpers import qapplication + app = qapplication() + install_progress = UpdateInstallation(None) + install_progress.show() + app.exec_() \ No newline at end of file diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py new file mode 100644 index 00000000000..2e4f9b6146c --- /dev/null +++ b/spyder/plugins/application/widgets/status.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# +# Copyright © Spyder Project Contributors +# Licensed under the terms of the MIT License +# (see spyder/__init__.py for details) + +""" +Status widget for Kite completions. +""" + +# Standard library imports +import logging + +# Third party imports +from qtpy.QtCore import Slot + +# Local imports +from spyder.api.widgets.status import StatusBarWidget +from spyder.config.base import _, running_under_pytest +#from spyder.plugins.application.container import INSTALLING +#from spyder.plugins.application.container import ( +# ApplicationContainer) +from spyder.plugins.application.widgets.install import ( + UpdateInstallerDialog) +from spyder.utils.icon_manager import ima + +logger = logging.getLogger(__name__) + + +class ApplicationUpdateStatus(StatusBarWidget): + """Status bar widget for Application update status.""" + BASE_TOOLTIP = _("Application update status") + DEFAULT_STATUS = _('not reachable') + ID = 'application_update_status' + + def __init__(self, parent): + self.tooltip = self.BASE_TOOLTIP + self.installation_thread = parent + super().__init__(parent) + + # Installation dialog + self.installer = UpdateInstallerDialog( + self, + self.installation_thread) + + self.installation_thread.sig_installation_status.connect( + self.set_value) + self.sig_clicked.connect(self.show_installation_dialog) + + def set_value(self, value): + """Return update installation state.""" + + + if value == "Installing": + self.tooltip = _("Update installation will continue in the " + "background.\n" + "Click here to show the installation " + "dialog again") + elif value is None: + value = self.DEFAULT_STATUS + self.tooltip = self.BASE_TOOLTIP + self.update_tooltip() + value = "Update: {0}".format(value) + super(ApplicationUpdateStatus, self).set_value(value) + + def get_tooltip(self): + """Reimplementation to get a dynamic tooltip.""" + return self.tooltip + + def get_icon(self): + return ima.icon('spyder') + + @Slot() + def show_installation_dialog(self): + """Show installation dialog.""" + self.installer.show() + + + + From 67767c5689fd3ae32f7d065839b62baf6bc59bd9 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Thu, 14 Jul 2022 09:52:32 -0500 Subject: [PATCH 02/49] WIP check updates --- spyder/plugins/application/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index 3558c428b68..e168655bde2 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -106,6 +106,7 @@ def on_statusbar_available(self): # Add status widget statusbar = self.get_plugin(Plugins.StatusBar) statusbar.add_status_widget(self.application_update_status) + # -------------------------- PLUGIN TEARDOWN ------------------------------ @on_plugin_teardown(plugin=Plugins.StatusBar) From d8f9f81146241e8fc9aea3f4da5ffb007e3b0044 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Fri, 15 Jul 2022 08:32:55 -0500 Subject: [PATCH 03/49] WIP check updates --- spyder/plugins/application/container.py | 10 ++- spyder/plugins/application/widgets/install.py | 74 +++++++++---------- spyder/plugins/application/widgets/status.py | 25 ++++++- 3 files changed, 64 insertions(+), 45 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index df44bb6d305..d6924b33787 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -276,7 +276,9 @@ def _progress_reporter(self, block_number, read_size, total_size): raise UpdateInstallationCancelledException() else: self.sig_download_progress.emit(progress, total_size) + def _cancell_thread_install_update(self): + self._change_update_installation_status(status=CANCELLED) self.thread_install_update.join() def _check_updates_ready(self): @@ -302,20 +304,22 @@ def _check_updates_ready(self): url_i = 'https://docs.spyder-ide.org/installation.html' def _download_install(): - with TemporaryDirectory(prefix = "Spyder-") as tmpdir: + with TemporaryDirectory(prefix = "Spyder-") as tmpdir: + self._change_update_installation_status(status=DOWNLOADING_INSTALLER) destination = os.path.join(tmpdir,'updateSpyder.exe') download =urlretrieve(url_r, destination, reporthook=self._progress_reporter) - self._change_update_installation_status(status="Installing") + self._change_update_installation_status(status=INSTALLING) install = subprocess.Popen(destination, shell=True) install.communicate() + self._change_update_installation_status(status=FINISHED) self.application_update_status.setVisible(False) def _thread_launcher(option): if option.text() in ('&Yes'): self.cancelled=False - """call a function in a simple thread, to prevent blocking""" self.application_update_status.setVisible(True) + """call a function in a simple thread, to prevent blocking""" self.thread_install_update = threading.Thread(target = _download_install) self.thread_install_update.start() # Define the custom QMessageBox diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index dfc2b8dedb1..5d371e5c4c0 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -4,7 +4,7 @@ # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) -"""Kite installation widget.""" +"""Update installation widget.""" # Standard library imports import sys @@ -43,6 +43,7 @@ def eventFilter(self, widget, event): return super(HoverEventFilter, self).eventFilter(widget, event) + class UpdateInstallation(QWidget): """Update progress installation widget.""" @@ -60,19 +61,15 @@ def __init__(self, parent): self._progress_widget.installEventFilter(self._progress_filter) self.cancel_button = QPushButton() self.cancel_button.setIcon(ima.icon('DialogCloseButton')) - self.cancel_button.hide() + #self.cancel_button.hide() progress_layout.addWidget(self._progress_bar, alignment=Qt.AlignLeft) progress_layout.addWidget(self.cancel_button) self._progress_widget.setLayout(progress_layout) self._progress_label = QLabel(_('Downloading')) - install_info = QLabel( - _("Dowloading the latest Spyder version
" - "which provides you with real time
" - "documentation as you code.

" - "When Kite is done installing, the Copilot will
" - "launch automatically and guide you through the
" - "rest of the setup process.")) + + self.install_info = QLabel( + _("Dowloading the latest Spyder version
")) button_layout = QHBoxLayout() self.ok_button = QPushButton(_('OK')) @@ -83,47 +80,49 @@ def __init__(self, parent): action_layout.addStretch() action_layout.addWidget(self._progress_label) action_layout.addWidget(self._progress_widget) - action_layout.addWidget(install_info) + action_layout.addWidget(self.install_info) action_layout.addSpacing(10) action_layout.addLayout(button_layout) action_layout.addStretch() # Right side - copilot_image_source = get_image_path('kite_copilot') + #copilot_image_source = get_image_path('kite_copilot') - copilot_image = QPixmap(copilot_image_source) - copilot_label = QLabel() + #copilot_image = QPixmap(copilot_image_source) + #copilot_label = QLabel() screen = QApplication.primaryScreen() device_pixel_ratio = screen.devicePixelRatio() - if device_pixel_ratio > 1: - copilot_image.setDevicePixelRatio(device_pixel_ratio) - copilot_label.setPixmap(copilot_image) - else: - image_height = int(copilot_image.height() * 0.4) - image_width = int(copilot_image.width() * 0.4) - copilot_label.setPixmap( - copilot_image.scaled(image_width, image_height, - Qt.KeepAspectRatio, - Qt.SmoothTransformation)) + #if device_pixel_ratio > 1: + #copilot_image.setDevicePixelRatio(device_pixel_ratio) + #copilot_label.setPixmap(copilot_image) + #else: + # image_height = int(copilot_image.height() * 0.4) + # image_width = int(copilot_image.width() * 0.4) + # copilot_label.setPixmap( + # copilot_image.scaled(image_width, image_height, + # Qt.KeepAspectRatio, + # Qt.SmoothTransformation)) # Layout general_layout = QHBoxLayout() general_layout.addLayout(action_layout) - general_layout.addWidget(copilot_label) + #general_layout.addWidget(copilot_label) self.setLayout(general_layout) # Signals - self._progress_filter.sig_hover_enter.connect( - lambda: self.cancel_button.show()) - self._progress_filter.sig_hover_leave.connect( - lambda: self.cancel_button.hide()) + #self._progress_filter.sig_hover_enter.connect( + # lambda: self.cancel_button.show()) + #self._progress_filter.sig_hover_leave.connect( + # lambda: self.cancel_button.hide()) def update_installation_status(self, status): """Update installation status (downloading, installing, finished).""" self._progress_label.setText(status) + self.install_info.setText(status + " the latest version of Spyder.") if status == INSTALLING: self._progress_bar.setRange(0, 0) + def update_installation_progress(self, current_value, total): """Update installation progress bar.""" @@ -154,6 +153,8 @@ def __init__(self, parent, installation_thread): self._installation_widget.update_installation_progress) self._installation_thread.sig_installation_status.connect( self._installation_widget.update_installation_status) + self._installation_thread.sig_installation_status.connect( + self.finished_installation) #self._installation_thread.sig_error_msg.connect(self._handle_error_msg) @@ -180,7 +181,7 @@ def _handle_error_msg(self, msg): def setup(self,installation=False): """Setup visibility of widgets.""" - self._installation_widget.setVisible(False) + self._installation_widget.setVisible(True) self.adjustSize() @@ -192,14 +193,18 @@ def cancel_install(self): QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self._installation_thread.cancelled = True - self._installation_thread._cancell_thread_install_update() + self._installation_thread._cancell_thread_install_update() self.setup() self.accept() return True return False - + def finished_installation(self, status): + """Handle finished installation.""" + if status == FINISHED: + self.setup() + self.accept() def close_installer(self): """Close the installation dialog.""" if (self._installation_thread.status == ERRORED @@ -217,10 +222,3 @@ def reject(self): self.close_installer() else: super(UpdateInstallerDialog, self).reject() - -if __name__ == "__main__": - from spyder.utils.qthelpers import qapplication - app = qapplication() - install_progress = UpdateInstallation(None) - install_progress.show() - app.exec_() \ No newline at end of file diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 2e4f9b6146c..70277560bf1 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -38,6 +38,7 @@ def __init__(self, parent): self.installation_thread = parent super().__init__(parent) + # Installation dialog self.installer = UpdateInstallerDialog( self, @@ -46,6 +47,7 @@ def __init__(self, parent): self.installation_thread.sig_installation_status.connect( self.set_value) self.sig_clicked.connect(self.show_installation_dialog) + def set_value(self, value): """Return update installation state.""" @@ -56,12 +58,27 @@ def set_value(self, value): "background.\n" "Click here to show the installation " "dialog again") - elif value is None: - value = self.DEFAULT_STATUS + elif value == "Downloading installer": + self.tootltip = ("Update installation will continue in the " + "background.\n" + "Click here to show the download " + "dialog again") + elif value == "Checking for updates": + self.tootltip= ("Update installation will continue in the " + "background.") + + else: + + self.setVisible(False) + value =self.DEFAULT_STATUS self.tooltip = self.BASE_TOOLTIP - self.update_tooltip() + + self.update_tooltip() value = "Update: {0}".format(value) - super(ApplicationUpdateStatus, self).set_value(value) + super().set_value(value)#super(ApplicationUpdateStatus, self).set_value(value) + + + #self.setVisible(False) def get_tooltip(self): """Reimplementation to get a dynamic tooltip.""" From 21214187e44077c7719faf2a7093a36974d7d8d1 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Mon, 18 Jul 2022 09:42:12 -0500 Subject: [PATCH 04/49] WIP check updates --- spyder/plugins/application/widgets/install.py | 45 ++----------------- spyder/plugins/application/widgets/status.py | 25 ++++------- 2 files changed, 11 insertions(+), 59 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 5d371e5c4c0..3d0d095f30d 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -11,17 +11,14 @@ # Third-party imports from qtpy.QtCore import QEvent, QObject, Qt, Signal -from qtpy.QtGui import QPixmap -from qtpy.QtWidgets import (QApplication, QDialog, QHBoxLayout, QMessageBox, +from qtpy.QtWidgets import (QDialog, QHBoxLayout, QMessageBox, QLabel, QProgressBar, QPushButton, QVBoxLayout, QWidget) # Local imports from spyder.config.base import _ -from spyder.utils.image_path_manager import get_image_path from spyder.utils.icon_manager import ima from spyder.utils.palette import QStylePalette -from spyder.utils.stylesheet import DialogStyle INSTALLING = _("Installing") FINISHED = _("Installation finished") @@ -83,25 +80,7 @@ def __init__(self, parent): action_layout.addWidget(self.install_info) action_layout.addSpacing(10) action_layout.addLayout(button_layout) - action_layout.addStretch() - - # Right side - #copilot_image_source = get_image_path('kite_copilot') - - #copilot_image = QPixmap(copilot_image_source) - #copilot_label = QLabel() - screen = QApplication.primaryScreen() - device_pixel_ratio = screen.devicePixelRatio() - #if device_pixel_ratio > 1: - #copilot_image.setDevicePixelRatio(device_pixel_ratio) - #copilot_label.setPixmap(copilot_image) - #else: - # image_height = int(copilot_image.height() * 0.4) - # image_width = int(copilot_image.width() * 0.4) - # copilot_label.setPixmap( - # copilot_image.scaled(image_width, image_height, - # Qt.KeepAspectRatio, - # Qt.SmoothTransformation)) + action_layout.addStretch() # Layout general_layout = QHBoxLayout() @@ -110,12 +89,6 @@ def __init__(self, parent): self.setLayout(general_layout) - # Signals - #self._progress_filter.sig_hover_enter.connect( - # lambda: self.cancel_button.show()) - #self._progress_filter.sig_hover_leave.connect( - # lambda: self.cancel_button.hide()) - def update_installation_status(self, status): """Update installation status (downloading, installing, finished).""" self._progress_label.setText(status) @@ -156,8 +129,6 @@ def __init__(self, parent, installation_thread): self._installation_thread.sig_installation_status.connect( self.finished_installation) - - #self._installation_thread.sig_error_msg.connect(self._handle_error_msg) self._installation_widget.ok_button.clicked.connect( self.close_installer) @@ -167,17 +138,7 @@ def __init__(self, parent, installation_thread): # Show integration widget self.setup() - def _handle_error_msg(self, msg): - """Handle error message with an error dialog.""" - '''QMessageBox.critical( - self._parent, - _('Kite installation error'), - _("An error ocurred while installing Kite!

" - "Please try to " - "install it manually or " - "contact Kite for help") - .format(kite_url=KITE_SPYDER_URL, kite_contact=KITE_CONTACT_URL))''' - self.accept() + def setup(self,installation=False): """Setup visibility of widgets.""" diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 70277560bf1..aac2a198c17 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -16,10 +16,7 @@ # Local imports from spyder.api.widgets.status import StatusBarWidget -from spyder.config.base import _, running_under_pytest -#from spyder.plugins.application.container import INSTALLING -#from spyder.plugins.application.container import ( -# ApplicationContainer) +from spyder.config.base import _ from spyder.plugins.application.widgets.install import ( UpdateInstallerDialog) from spyder.utils.icon_manager import ima @@ -53,22 +50,15 @@ def set_value(self, value): """Return update installation state.""" - if value == "Installing": + if value == "Downloading installer" or value == "Installing" : + self.setVisible(True) self.tooltip = _("Update installation will continue in the " "background.\n" "Click here to show the installation " "dialog again") - elif value == "Downloading installer": - self.tootltip = ("Update installation will continue in the " - "background.\n" - "Click here to show the download " - "dialog again") - elif value == "Checking for updates": - self.tootltip= ("Update installation will continue in the " - "background.") - - else: - + elif value == "Checking for updates" : + self.tooltip = self.BASE_TOOLTIP + else: self.setVisible(False) value =self.DEFAULT_STATUS self.tooltip = self.BASE_TOOLTIP @@ -90,7 +80,8 @@ def get_icon(self): @Slot() def show_installation_dialog(self): """Show installation dialog.""" - self.installer.show() + if (not self.tooltip==self.BASE_TOOLTIP): + self.installer.show() From 5384e703e53a934f3101674ce6fea0cb3d0a5214 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Mon, 18 Jul 2022 09:42:32 -0500 Subject: [PATCH 05/49] WIP check updates --- spyder/plugins/application/container.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index d6924b33787..ab2577846db 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -78,13 +78,12 @@ class ApplicationActions: SpyderRestart = "Restart" SpyderRestartDebug = "Restart in debug mode" -# Installation process statuses +# Update Installation process statuses NO_STATUS = _("No status") -DOWNLOADING_SCRIPT = _("Downloading script installer") DOWNLOADING_INSTALLER = _("Downloading installer") INSTALLING = _("Installing") FINISHED = _("Installation finished") -ERRORED = _("Installation errored") +CHECKING = _("Checking for updates") CANCELLED = _("Cancelled") class ApplicationContainer(PluginMainContainer): @@ -282,6 +281,7 @@ def _cancell_thread_install_update(self): self.thread_install_update.join() def _check_updates_ready(self): + """Show results of the Spyder update checking process.""" # `feedback` = False is used on startup, so only positive feedback is @@ -290,6 +290,7 @@ def _check_updates_ready(self): feedback = self.give_updates_feedback # Get results from worker + update_available = self.worker_updates.update_available latest_release = self.worker_updates.latest_release error_msg = self.worker_updates.error @@ -304,8 +305,7 @@ def _check_updates_ready(self): url_i = 'https://docs.spyder-ide.org/installation.html' def _download_install(): - with TemporaryDirectory(prefix = "Spyder-") as tmpdir: - self._change_update_installation_status(status=DOWNLOADING_INSTALLER) + with TemporaryDirectory(prefix = "Spyder-") as tmpdir: destination = os.path.join(tmpdir,'updateSpyder.exe') download =urlretrieve(url_r, destination, @@ -314,14 +314,15 @@ def _download_install(): install = subprocess.Popen(destination, shell=True) install.communicate() self._change_update_installation_status(status=FINISHED) - self.application_update_status.setVisible(False) def _thread_launcher(option): if option.text() in ('&Yes'): self.cancelled=False - self.application_update_status.setVisible(True) + self._change_update_installation_status(status=DOWNLOADING_INSTALLER) """call a function in a simple thread, to prevent blocking""" self.thread_install_update = threading.Thread(target = _download_install) self.thread_install_update.start() + else: + self.application_update_status.setVisible(False) # Define the custom QMessageBox box = MessageCheckBox(icon=QMessageBox.Information, parent=self) @@ -401,7 +402,7 @@ def check_updates(self, startup=False): """Check for spyder updates on github releases using a QThread.""" # Disable check_updates_action while the thread is working self.check_updates_action.setDisabled(True) - + self._change_update_installation_status(status=CHECKING) if self.thread_updates is not None: self.thread_updates.quit() self.thread_updates.wait() From 5d874d7ab9c80ade3d50049d948c4654962a55c0 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 26 Jul 2022 01:11:40 -0500 Subject: [PATCH 06/49] WIP check updates --- spyder/plugins/application/container.py | 122 +++++++++++------- spyder/plugins/application/widgets/install.py | 48 +++---- spyder/plugins/application/widgets/status.py | 32 ++--- 3 files changed, 107 insertions(+), 95 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index ab2577846db..dc3f3ec3f6b 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -48,13 +48,16 @@ # Localization _ = get_translation('spyder') + class ApplicationPluginMenus: DebugLogsMenu = "debug_logs_menu" + class LogsMenuSections: SpyderLogSection = "spyder_log_section" LSPLogsSection = "lsp_logs_section" + # Actions class ApplicationActions: # Help @@ -78,24 +81,27 @@ class ApplicationActions: SpyderRestart = "Restart" SpyderRestartDebug = "Restart in debug mode" + # Update Installation process statuses NO_STATUS = _("No status") DOWNLOADING_INSTALLER = _("Downloading installer") INSTALLING = _("Installing") FINISHED = _("Installation finished") +PENDING = _("Update ready") CHECKING = _("Checking for updates") CANCELLED = _("Cancelled") + class ApplicationContainer(PluginMainContainer): # Signals - # Signal to get the current status of the installation + # Signal to get the current status of the update installation # str: Status string sig_installation_status = Signal(str) # Signal to get the download progress - # str: Download progress - # str: Total download size + # int: Download progress + # int: Total download size sig_download_progress = Signal(int, int) sig_report_issue_requested = Signal() @@ -114,9 +120,11 @@ class ApplicationContainer(PluginMainContainer): def __init__(self, name, plugin, parent=None): super().__init__(name, plugin, parent) + # Windows(OS) update installation self.cancelled = False - self.status=NO_STATUS - self.thread_install_update=None + self.status = NO_STATUS + self.thread_install_update = None + # Keep track of dpi message self.current_dpi = None self.dpi_messagebox = None @@ -124,11 +132,12 @@ def __init__(self, name, plugin, parent=None): # ---- PluginMainContainer API # ------------------------------------------------------------------------- def setup(self): - # Compute dependencies in a thread to not block the interface. + self.application_update_status = ApplicationUpdateStatus(parent=self) + self._change_update_installation_status(status=NO_STATUS) + # Compute dependencies in a thread to not block the interface. self.dependencies_thread = QThread(None) - # Attributes self.dialog_manager = DialogManager() self.give_updates_feedback = False @@ -268,29 +277,58 @@ def _change_update_installation_status(self, status=NO_STATUS): self.sig_installation_status.emit(self.status) def _progress_reporter(self, block_number, read_size, total_size): - progress = 0 - if total_size > 0: - progress = block_number * read_size - if self.cancelled: - raise UpdateInstallationCancelledException() - else: - self.sig_download_progress.emit(progress, total_size) + progress = 0 + if total_size > 0: + progress = block_number * read_size + if self.cancelled: + raise UpdateInstallationCancelledException() + else: + self.sig_download_progress.emit(progress, total_size) def _cancell_thread_install_update(self): self._change_update_installation_status(status=CANCELLED) self.thread_install_update.join() - + + def _download_install(self): + try: + with TemporaryDirectory(prefix="Spyder-") as tmpdir: + destination = os.path.join(tmpdir, 'updateSpyder.exe') + download = urlretrieve( + ('https://github.com/spyder-ide/spyder/releases/latest/' + 'download/Spyder_64bit_full.exe'), + destination, + reporthook=self._progress_reporter) + self._change_update_installation_status(status=INSTALLING) + install = subprocess.Popen(destination, shell=True) + install.communicate() + except UpdateInstallationCancelledException: + self._change_update_installation_status(status=CANCELLED) + finally: + self._change_update_installation_status(status=PENDING) + + def _thread_launcher(self, option): + if option.text() in ('&Yes'): + self.cancelled = False + self._change_update_installation_status( + status=DOWNLOADING_INSTALLER) + """call a function in a simple thread, to prevent blocking""" + self.thread_install_update = threading.Thread( + target=self._download_install) + self.thread_install_update.start() + else: + self._change_update_installation_status(status=PENDING) + def _check_updates_ready(self): - + """Show results of the Spyder update checking process.""" # `feedback` = False is used on startup, so only positive feedback is # given. `feedback` = True is used when after startup (when using the # menu action, and gives feeback if updates are, or are not found. feedback = self.give_updates_feedback - + # Get results from worker - + update_available = self.worker_updates.update_available latest_release = self.worker_updates.latest_release error_msg = self.worker_updates.error @@ -302,27 +340,8 @@ def _check_updates_ready(self): else: url_r = ('https://github.com/spyder-ide/spyder/releases/latest/' 'download/Spyder_64bit_full.exe') - url_i = 'https://docs.spyder-ide.org/installation.html' - - def _download_install(): - with TemporaryDirectory(prefix = "Spyder-") as tmpdir: - destination = os.path.join(tmpdir,'updateSpyder.exe') - download =urlretrieve(url_r, - destination, - reporthook=self._progress_reporter) - self._change_update_installation_status(status=INSTALLING) - install = subprocess.Popen(destination, shell=True) - install.communicate() - self._change_update_installation_status(status=FINISHED) - def _thread_launcher(option): - if option.text() in ('&Yes'): - self.cancelled=False - self._change_update_installation_status(status=DOWNLOADING_INSTALLER) - """call a function in a simple thread, to prevent blocking""" - self.thread_install_update = threading.Thread(target = _download_install) - self.thread_install_update.start() - else: - self.application_update_status.setVisible(False) + url_i = 'https://docs.spyder-ide.org/installation.html' + # Define the custom QMessageBox box = MessageCheckBox(icon=QMessageBox.Information, parent=self) @@ -344,9 +363,9 @@ def _thread_launcher(option): box.exec_() check_updates = box.is_checked() else: - + if True: - + header = _("Spyder {} is available!

").format( latest_release) footer = _( @@ -364,13 +383,16 @@ def _thread_launcher(option): "conda update anaconda
" "conda install spyder={}

" ).format(latest_release) + self._change_update_installation_status(status=NO_STATUS) else: if os.name == 'nt': - box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) + box.setStandardButtons(QMessageBox.Yes | + QMessageBox.No) content = _( - "You want to download and install the latest version of spyder?

" + "You want to download and install the latest " + "version of spyder?

" ) - box.buttonClicked.connect(_thread_launcher) + box.buttonClicked.connect(self._thread_launcher) else: content = _( "Click this link to " @@ -387,6 +409,9 @@ def _thread_launcher(option): box.set_check_visible(False) box.exec_() check_updates = box.is_checked() + self._change_update_installation_status(status=NO_STATUS) + else: + self._change_update_installation_status(status=NO_STATUS) # Update checkbox based on user interaction self.set_conf(option, check_updates) @@ -402,7 +427,11 @@ def check_updates(self, startup=False): """Check for spyder updates on github releases using a QThread.""" # Disable check_updates_action while the thread is working self.check_updates_action.setDisabled(True) - self._change_update_installation_status(status=CHECKING) + if os.name == 'nt': + self._change_update_installation_status(status=CHECKING) + else: + self._change_update_installation_status(status=NO_STATUS) + if self.thread_updates is not None: self.thread_updates.quit() self.thread_updates.wait() @@ -673,7 +702,6 @@ def show_dpi_change_message(self, dpi): self.dpi_messagebox.adjustSize() - class UpdateInstallationCancelledException(Exception): """Update installation was cancelled.""" - pass \ No newline at end of file + pass diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 3d0d095f30d..b671b4cfc01 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -24,21 +24,7 @@ FINISHED = _("Installation finished") ERRORED = _("Installation errored") CANCELLED = _("Cancelled") -class HoverEventFilter(QObject): - """QObject to handle event filtering.""" - # Signal to trigger on a HoverEnter event - sig_hover_enter = Signal() - # Signal to trigger on a HoverLeave event - sig_hover_leave = Signal() - - def eventFilter(self, widget, event): - """Reimplemented Qt method.""" - if event.type() == QEvent.HoverEnter: - self.sig_hover_enter.emit() - elif event.type() == QEvent.HoverLeave: - self.sig_hover_leave.emit() - - return super(HoverEventFilter, self).eventFilter(widget, event) +PENDING = _("Update ready") class UpdateInstallation(QWidget): @@ -52,13 +38,10 @@ def __init__(self, parent): progress_layout = QHBoxLayout() self._progress_widget = QWidget(self) self._progress_widget.setFixedHeight(50) - self._progress_filter = HoverEventFilter() self._progress_bar = QProgressBar(self) self._progress_bar.setFixedWidth(180) - self._progress_widget.installEventFilter(self._progress_filter) self.cancel_button = QPushButton() self.cancel_button.setIcon(ima.icon('DialogCloseButton')) - #self.cancel_button.hide() progress_layout.addWidget(self._progress_bar, alignment=Qt.AlignLeft) progress_layout.addWidget(self.cancel_button) self._progress_widget.setLayout(progress_layout) @@ -80,12 +63,11 @@ def __init__(self, parent): action_layout.addWidget(self.install_info) action_layout.addSpacing(10) action_layout.addLayout(button_layout) - action_layout.addStretch() + action_layout.addStretch() # Layout general_layout = QHBoxLayout() general_layout.addLayout(action_layout) - #general_layout.addWidget(copilot_label) self.setLayout(general_layout) @@ -95,7 +77,7 @@ def update_installation_status(self, status): self.install_info.setText(status + " the latest version of Spyder.") if status == INSTALLING: self._progress_bar.setRange(0, 0) - + self.cancel_button.hide() def update_installation_progress(self, current_value, total): """Update installation progress bar.""" @@ -127,8 +109,7 @@ def __init__(self, parent, installation_thread): self._installation_thread.sig_installation_status.connect( self._installation_widget.update_installation_status) self._installation_thread.sig_installation_status.connect( - self.finished_installation) - + self.finished_installation) self._installation_widget.ok_button.clicked.connect( self.close_installer) @@ -138,14 +119,11 @@ def __init__(self, parent, installation_thread): # Show integration widget self.setup() - - - def setup(self,installation=False): + def setup(self, installation=False): """Setup visibility of widgets.""" self._installation_widget.setVisible(True) self.adjustSize() - def cancel_install(self): """Cancel the installation in progress.""" reply = QMessageBox.critical( @@ -154,18 +132,30 @@ def cancel_install(self): QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self._installation_thread.cancelled = True - self._installation_thread._cancell_thread_install_update() + self._installation_thread._cancell_thread_install_update() self.setup() self.accept() return True return False + def continue_install(self): + """Cancel the installation in progress.""" + reply = QMessageBox(icon=QMessageBox.Question, + text=_('Do you really want to cancel Update' + 'installation?'), + parent=self) + reply.setWindowTitle("Spyder") + reply.setAttribute(Qt.WA_ShowWithoutActivating) + reply.setStandardButtons(QMessageBox.Yes | QMessageBox.No) + reply.buttonClicked.connect(self._installation_thread._thread_launcher) + reply.show() def finished_installation(self, status): """Handle finished installation.""" - if status == FINISHED: + if status == FINISHED or status == PENDING: self.setup() self.accept() + def close_installer(self): """Close the installation dialog.""" if (self._installation_thread.status == ERRORED diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index aac2a198c17..572de4fc9e3 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -35,7 +35,6 @@ def __init__(self, parent): self.installation_thread = parent super().__init__(parent) - # Installation dialog self.installer = UpdateInstallerDialog( self, @@ -44,31 +43,27 @@ def __init__(self, parent): self.installation_thread.sig_installation_status.connect( self.set_value) self.sig_clicked.connect(self.show_installation_dialog) - def set_value(self, value): """Return update installation state.""" - - if value == "Downloading installer" or value == "Installing" : + if value == "Downloading installer" or value == "Installing": self.setVisible(True) self.tooltip = _("Update installation will continue in the " "background.\n" "Click here to show the installation " "dialog again") - elif value == "Checking for updates" : - self.tooltip = self.BASE_TOOLTIP - else: + elif value == "Checking for updates" or value == "Update ready": + self.setVisible(True) + self.tooltip = value + else: self.setVisible(False) - value =self.DEFAULT_STATUS + value = self.DEFAULT_STATUS self.tooltip = self.BASE_TOOLTIP - - self.update_tooltip() - value = "Update: {0}".format(value) - super().set_value(value)#super(ApplicationUpdateStatus, self).set_value(value) - - #self.setVisible(False) + self.update_tooltip() + value = "Update: {0}".format(value) + super().set_value(value) def get_tooltip(self): """Reimplementation to get a dynamic tooltip.""" @@ -80,9 +75,8 @@ def get_icon(self): @Slot() def show_installation_dialog(self): """Show installation dialog.""" - if (not self.tooltip==self.BASE_TOOLTIP): + if (not self.tooltip == self.BASE_TOOLTIP and not + self.tooltip == "Update ready"): self.installer.show() - - - - + elif (self.tooltip == "Update ready"): + self.installer.continue_install() From 5d87045cb01aa6d8e2afc099cbfa7c6f3e2126da Mon Sep 17 00:00:00 2001 From: jsbautista Date: Thu, 28 Jul 2022 14:31:54 -0500 Subject: [PATCH 07/49] WIP check updates --- spyder/plugins/application/container.py | 24 +++++++++---------- spyder/plugins/application/plugin.py | 2 +- .../plugins/application/widgets/__init__.py | 8 +++++++ spyder/plugins/application/widgets/install.py | 13 ++++------ spyder/plugins/application/widgets/status.py | 19 ++++++++------- 5 files changed, 36 insertions(+), 30 deletions(-) create mode 100644 spyder/plugins/application/widgets/__init__.py diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index dc3f3ec3f6b..dd9f2b1b28f 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -49,6 +49,16 @@ _ = get_translation('spyder') +# Update Installation process statuses +NO_STATUS = _("No status") +DOWNLOADING_INSTALLER = _("Downloading installer") +INSTALLING = _("Installing") +FINISHED = _("Installation finished") +PENDING = _("Update ready") +CHECKING = _("Checking for updates") +CANCELLED = _("Cancelled") + + class ApplicationPluginMenus: DebugLogsMenu = "debug_logs_menu" @@ -82,16 +92,6 @@ class ApplicationActions: SpyderRestartDebug = "Restart in debug mode" -# Update Installation process statuses -NO_STATUS = _("No status") -DOWNLOADING_INSTALLER = _("Downloading installer") -INSTALLING = _("Installing") -FINISHED = _("Installation finished") -PENDING = _("Update ready") -CHECKING = _("Checking for updates") -CANCELLED = _("Cancelled") - - class ApplicationContainer(PluginMainContainer): # Signals @@ -364,7 +364,7 @@ def _check_updates_ready(self): check_updates = box.is_checked() else: - if True: + if update_available: header = _("Spyder {} is available!

").format( latest_release) @@ -372,7 +372,7 @@ def _check_updates_ready(self): "For more information visit our " "installation guide." ).format(url_i) - if not is_anaconda(): + if is_anaconda(): content = _( "Important note: Since you installed " "Spyder with Anaconda, please don't use " diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index e168655bde2..11a534bf1b8 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -106,9 +106,9 @@ def on_statusbar_available(self): # Add status widget statusbar = self.get_plugin(Plugins.StatusBar) statusbar.add_status_widget(self.application_update_status) - # -------------------------- PLUGIN TEARDOWN ------------------------------ + @on_plugin_teardown(plugin=Plugins.StatusBar) def on_statusbar_teardown(self): # Add status widget diff --git a/spyder/plugins/application/widgets/__init__.py b/spyder/plugins/application/widgets/__init__.py new file mode 100644 index 00000000000..8b6d0e5dda2 --- /dev/null +++ b/spyder/plugins/application/widgets/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# +# Copyright © Spyder Project Contributors +# +# Licensed under the terms of the MIT License +# (see spyder/__init__.py for details) + +"""Widgets for the application plugin.""" diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index b671b4cfc01..23a18d7c516 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -19,12 +19,8 @@ from spyder.config.base import _ from spyder.utils.icon_manager import ima from spyder.utils.palette import QStylePalette - -INSTALLING = _("Installing") -FINISHED = _("Installation finished") -ERRORED = _("Installation errored") -CANCELLED = _("Cancelled") -PENDING = _("Update ready") +from spyder.plugins.application.container import ( + INSTALLING, FINISHED, CANCELLED, PENDING) class UpdateInstallation(QWidget): @@ -86,7 +82,7 @@ def update_installation_progress(self, current_value, total): class UpdateInstallerDialog(QDialog): - """Kite installer.""" + """Spyder installer.""" def __init__(self, parent, installation_thread): super(UpdateInstallerDialog, self).__init__(parent) @@ -158,8 +154,7 @@ def finished_installation(self, status): def close_installer(self): """Close the installation dialog.""" - if (self._installation_thread.status == ERRORED - or self._installation_thread.status == FINISHED + if (self._installation_thread.status == FINISHED or self._installation_thread.status == CANCELLED): self.setup() self.accept() diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 572de4fc9e3..8b527aff988 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -20,6 +20,9 @@ from spyder.plugins.application.widgets.install import ( UpdateInstallerDialog) from spyder.utils.icon_manager import ima +from spyder.plugins.application.container import ( + INSTALLING, PENDING, DOWNLOADING_INSTALLER, CHECKING) +from spyder import get_versions logger = logging.getLogger(__name__) @@ -27,10 +30,10 @@ class ApplicationUpdateStatus(StatusBarWidget): """Status bar widget for Application update status.""" BASE_TOOLTIP = _("Application update status") - DEFAULT_STATUS = _('not reachable') ID = 'application_update_status' def __init__(self, parent): + self.tooltip = self.BASE_TOOLTIP self.installation_thread = parent super().__init__(parent) @@ -46,23 +49,23 @@ def __init__(self, parent): def set_value(self, value): """Return update installation state.""" - - if value == "Downloading installer" or value == "Installing": + versions = get_versions() + if value == DOWNLOADING_INSTALLER or value == INSTALLING: self.setVisible(True) self.tooltip = _("Update installation will continue in the " "background.\n" "Click here to show the installation " "dialog again") - elif value == "Checking for updates" or value == "Update ready": + elif value == CHECKING or value == PENDING: self.setVisible(True) self.tooltip = value else: self.setVisible(False) - value = self.DEFAULT_STATUS + value = versions['spyder'] self.tooltip = self.BASE_TOOLTIP self.update_tooltip() - value = "Update: {0}".format(value) + value = "Spyder: {0}".format(value) super().set_value(value) def get_tooltip(self): @@ -76,7 +79,7 @@ def get_icon(self): def show_installation_dialog(self): """Show installation dialog.""" if (not self.tooltip == self.BASE_TOOLTIP and not - self.tooltip == "Update ready"): + self.tooltip == PENDING): self.installer.show() - elif (self.tooltip == "Update ready"): + elif (self.tooltip == PENDING): self.installer.continue_install() From 929d924efa278e451eed48499b60c717264d508c Mon Sep 17 00:00:00 2001 From: jsbautista Date: Thu, 28 Jul 2022 15:18:19 -0500 Subject: [PATCH 08/49] WIP check updates --- spyder/plugins/application/widgets/install.py | 12 ++++++++++-- spyder/plugins/application/widgets/status.py | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 23a18d7c516..29ecceb399e 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -19,8 +19,16 @@ from spyder.config.base import _ from spyder.utils.icon_manager import ima from spyder.utils.palette import QStylePalette -from spyder.plugins.application.container import ( - INSTALLING, FINISHED, CANCELLED, PENDING) + + +# Update Installation process statusesf +NO_STATUS = _("No status") +DOWNLOADING_INSTALLER = _("Downloading installer") +INSTALLING = _("Installing") +FINISHED = _("Installation finished") +PENDING = _("Update ready") +CHECKING = _("Checking for updates") +CANCELLED = _("Cancelled") class UpdateInstallation(QWidget): diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 8b527aff988..9afe9cf2aa6 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -20,13 +20,21 @@ from spyder.plugins.application.widgets.install import ( UpdateInstallerDialog) from spyder.utils.icon_manager import ima -from spyder.plugins.application.container import ( - INSTALLING, PENDING, DOWNLOADING_INSTALLER, CHECKING) from spyder import get_versions logger = logging.getLogger(__name__) +# Update Installation process statusesf +NO_STATUS = _("No status") +DOWNLOADING_INSTALLER = _("Downloading installer") +INSTALLING = _("Installing") +FINISHED = _("Installation finished") +PENDING = _("Update ready") +CHECKING = _("Checking for updates") +CANCELLED = _("Cancelled") + + class ApplicationUpdateStatus(StatusBarWidget): """Status bar widget for Application update status.""" BASE_TOOLTIP = _("Application update status") From 437297ac530411e48c1521aefe2eef8328413a74 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 2 Aug 2022 13:41:16 -0500 Subject: [PATCH 09/49] WIP check updates --- spyder/plugins/application/container.py | 10 +++++----- spyder/plugins/application/widgets/install.py | 12 ++++++------ spyder/plugins/application/widgets/status.py | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index dd9f2b1b28f..5498279b2e0 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -49,12 +49,12 @@ _ = get_translation('spyder') -# Update Installation process statuses +# Update Installation process statusesf NO_STATUS = _("No status") DOWNLOADING_INSTALLER = _("Downloading installer") INSTALLING = _("Installing") FINISHED = _("Installation finished") -PENDING = _("Update ready") +PENDING = _("Pending update") CHECKING = _("Checking for updates") CANCELLED = _("Cancelled") @@ -285,7 +285,7 @@ def _progress_reporter(self, block_number, read_size, total_size): else: self.sig_download_progress.emit(progress, total_size) - def _cancell_thread_install_update(self): + def cancell_thread_install_update(self): self._change_update_installation_status(status=CANCELLED) self.thread_install_update.join() @@ -306,7 +306,7 @@ def _download_install(self): finally: self._change_update_installation_status(status=PENDING) - def _thread_launcher(self, option): + def installation_thread(self, option): if option.text() in ('&Yes'): self.cancelled = False self._change_update_installation_status( @@ -392,7 +392,7 @@ def _check_updates_ready(self): "You want to download and install the latest " "version of spyder?

" ) - box.buttonClicked.connect(self._thread_launcher) + box.buttonClicked.connect(self.installation_thread) else: content = _( "Click this link to " diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 29ecceb399e..738d1bef400 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -26,7 +26,7 @@ DOWNLOADING_INSTALLER = _("Downloading installer") INSTALLING = _("Installing") FINISHED = _("Installation finished") -PENDING = _("Update ready") +PENDING = _("Pending update") CHECKING = _("Checking for updates") CANCELLED = _("Cancelled") @@ -136,7 +136,7 @@ def cancel_install(self): QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self._installation_thread.cancelled = True - self._installation_thread._cancell_thread_install_update() + self._installation_thread.cancell_thread_install_update() self.setup() self.accept() return True @@ -145,13 +145,13 @@ def cancel_install(self): def continue_install(self): """Cancel the installation in progress.""" reply = QMessageBox(icon=QMessageBox.Question, - text=_('Do you really want to cancel Update' - 'installation?'), - parent=self) + text=_('Do you want to download and install the latest version of' + ' spyder?
'), + parent=self._parent) reply.setWindowTitle("Spyder") reply.setAttribute(Qt.WA_ShowWithoutActivating) reply.setStandardButtons(QMessageBox.Yes | QMessageBox.No) - reply.buttonClicked.connect(self._installation_thread._thread_launcher) + reply.buttonClicked.connect(self._installation_thread.installation_thread) reply.show() def finished_installation(self, status): diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 9afe9cf2aa6..4d8853add30 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -30,7 +30,7 @@ DOWNLOADING_INSTALLER = _("Downloading installer") INSTALLING = _("Installing") FINISHED = _("Installation finished") -PENDING = _("Update ready") +PENDING = _("Pending update") CHECKING = _("Checking for updates") CANCELLED = _("Cancelled") From d381e287d143678b7ca0a65a931c3dcd43b6c806 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 2 Aug 2022 13:54:46 -0500 Subject: [PATCH 10/49] WIP check updates --- spyder/plugins/application/widgets/install.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 738d1bef400..8c9411a2550 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -145,13 +145,15 @@ def cancel_install(self): def continue_install(self): """Cancel the installation in progress.""" reply = QMessageBox(icon=QMessageBox.Question, - text=_('Do you want to download and install the latest version of' - ' spyder?
'), + text=_('Do you want to download and' + ' install the latest version of' + 'spyder?
'), parent=self._parent) reply.setWindowTitle("Spyder") reply.setAttribute(Qt.WA_ShowWithoutActivating) reply.setStandardButtons(QMessageBox.Yes | QMessageBox.No) - reply.buttonClicked.connect(self._installation_thread.installation_thread) + reply.buttonClicked.connect( + self._installation_thread.installation_thread) reply.show() def finished_installation(self, status): From 4e76de8d7db215652ed3caf40c44f65671fc402f Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Thu, 4 Aug 2022 23:40:18 -0500 Subject: [PATCH 11/49] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/widgets/install.py | 4 ++-- spyder/plugins/application/widgets/status.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 8c9411a2550..c08a491b5a4 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -21,7 +21,7 @@ from spyder.utils.palette import QStylePalette -# Update Installation process statusesf +# Update installation process statuses NO_STATUS = _("No status") DOWNLOADING_INSTALLER = _("Downloading installer") INSTALLING = _("Installing") @@ -78,7 +78,7 @@ def __init__(self, parent): def update_installation_status(self, status): """Update installation status (downloading, installing, finished).""" self._progress_label.setText(status) - self.install_info.setText(status + " the latest version of Spyder.") + self.install_info.setText(status + _(" the latest version of Spyder.")) if status == INSTALLING: self._progress_bar.setRange(0, 0) self.cancel_button.hide() diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 4d8853add30..3a40b09a446 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -5,7 +5,7 @@ # (see spyder/__init__.py for details) """ -Status widget for Kite completions. +Status widget for Spyder updates. """ # Standard library imports From 85153d72c90ef2474ea257f7a0994c0b62e1ffac Mon Sep 17 00:00:00 2001 From: jsbautista Date: Fri, 5 Aug 2022 09:45:15 -0500 Subject: [PATCH 12/49] WIP check updates --- spyder/plugins/application/container.py | 86 ++--------------- spyder/plugins/application/widgets/install.py | 92 ++++++++++++++++--- spyder/plugins/application/widgets/status.py | 16 +++- 3 files changed, 102 insertions(+), 92 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 5498279b2e0..1575377391d 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -14,10 +14,6 @@ import os import sys import glob -import subprocess -from urllib.request import urlretrieve -from tempfile import TemporaryDirectory -import threading # Third party imports from qtpy.QtCore import Qt, QThread, QTimer, Signal, Slot @@ -31,7 +27,7 @@ from spyder.api.widgets.main_container import PluginMainContainer from spyder.utils.installers import InstallerMissingDependencies from spyder.config.utils import is_anaconda -from spyder.config.base import get_conf_path, get_debug_level +from spyder.config.base import get_conf_path, get_debug_level, is_pynsist from spyder.plugins.console.api import ConsoleActions from spyder.utils.qthelpers import start_file, DialogManager from spyder.widgets.about import AboutDialog @@ -49,7 +45,7 @@ _ = get_translation('spyder') -# Update Installation process statusesf +# Update Installation process statuses NO_STATUS = _("No status") DOWNLOADING_INSTALLER = _("Downloading installer") INSTALLING = _("Installing") @@ -94,16 +90,6 @@ class ApplicationActions: class ApplicationContainer(PluginMainContainer): - # Signals - # Signal to get the current status of the update installation - # str: Status string - sig_installation_status = Signal(str) - - # Signal to get the download progress - # int: Download progress - # int: Total download size - sig_download_progress = Signal(int, int) - sig_report_issue_requested = Signal() """ Signal to request reporting an issue to Github. @@ -120,11 +106,7 @@ class ApplicationContainer(PluginMainContainer): def __init__(self, name, plugin, parent=None): super().__init__(name, plugin, parent) - # Windows(OS) update installation - self.cancelled = False - self.status = NO_STATUS - self.thread_install_update = None - + # Keep track of dpi message self.current_dpi = None self.dpi_messagebox = None @@ -134,7 +116,7 @@ def __init__(self, name, plugin, parent=None): def setup(self): self.application_update_status = ApplicationUpdateStatus(parent=self) - self._change_update_installation_status(status=NO_STATUS) + self.application_update_status.set_value(NO_STATUS) # Compute dependencies in a thread to not block the interface. self.dependencies_thread = QThread(None) @@ -271,52 +253,6 @@ def show_windows_env_variables(self): # ---- Updates # ------------------------------------------------------------------------- - def _change_update_installation_status(self, status=NO_STATUS): - """Set the installation status.""" - self.status = status - self.sig_installation_status.emit(self.status) - - def _progress_reporter(self, block_number, read_size, total_size): - progress = 0 - if total_size > 0: - progress = block_number * read_size - if self.cancelled: - raise UpdateInstallationCancelledException() - else: - self.sig_download_progress.emit(progress, total_size) - - def cancell_thread_install_update(self): - self._change_update_installation_status(status=CANCELLED) - self.thread_install_update.join() - - def _download_install(self): - try: - with TemporaryDirectory(prefix="Spyder-") as tmpdir: - destination = os.path.join(tmpdir, 'updateSpyder.exe') - download = urlretrieve( - ('https://github.com/spyder-ide/spyder/releases/latest/' - 'download/Spyder_64bit_full.exe'), - destination, - reporthook=self._progress_reporter) - self._change_update_installation_status(status=INSTALLING) - install = subprocess.Popen(destination, shell=True) - install.communicate() - except UpdateInstallationCancelledException: - self._change_update_installation_status(status=CANCELLED) - finally: - self._change_update_installation_status(status=PENDING) - - def installation_thread(self, option): - if option.text() in ('&Yes'): - self.cancelled = False - self._change_update_installation_status( - status=DOWNLOADING_INSTALLER) - """call a function in a simple thread, to prevent blocking""" - self.thread_install_update = threading.Thread( - target=self._download_install) - self.thread_install_update.start() - else: - self._change_update_installation_status(status=PENDING) def _check_updates_ready(self): @@ -365,6 +301,7 @@ def _check_updates_ready(self): else: if update_available: + self.application_update_status.set_value(PENDING) header = _("Spyder {} is available!

").format( latest_release) @@ -383,16 +320,16 @@ def _check_updates_ready(self): "conda update anaconda
" "conda install spyder={}

" ).format(latest_release) - self._change_update_installation_status(status=NO_STATUS) + self.application_update_status.set_value(NO_STATUS) else: - if os.name == 'nt': + if os.name == 'nt' and is_pynsist(): box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) content = _( "You want to download and install the latest " "version of spyder?

" ) - box.buttonClicked.connect(self.installation_thread) + box.buttonClicked.connect(self.application_update_status.start_installation) else: content = _( "Click this link to " @@ -409,9 +346,6 @@ def _check_updates_ready(self): box.set_check_visible(False) box.exec_() check_updates = box.is_checked() - self._change_update_installation_status(status=NO_STATUS) - else: - self._change_update_installation_status(status=NO_STATUS) # Update checkbox based on user interaction self.set_conf(option, check_updates) @@ -428,9 +362,9 @@ def check_updates(self, startup=False): # Disable check_updates_action while the thread is working self.check_updates_action.setDisabled(True) if os.name == 'nt': - self._change_update_installation_status(status=CHECKING) + self.application_update_status.set_value(CHECKING) else: - self._change_update_installation_status(status=NO_STATUS) + self.application_update_status.set_value(NO_STATUS) if self.thread_updates is not None: self.thread_updates.quit() diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index c08a491b5a4..f6210b5c46f 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -8,6 +8,10 @@ # Standard library imports import sys +import subprocess +from urllib.request import urlretrieve +from tempfile import TemporaryDirectory +import threading # Third-party imports from qtpy.QtCore import QEvent, QObject, Qt, Signal @@ -92,13 +96,27 @@ def update_installation_progress(self, current_value, total): class UpdateInstallerDialog(QDialog): """Spyder installer.""" - def __init__(self, parent, installation_thread): + # Signal to get the download progress + # int: Download progress + # int: Total download size + sig_download_progress = Signal(int, int) + + # Signals + # Signal to get the current status of the update installation + # str: Status string + sig_installation_status = Signal(str) + + + def __init__(self, parent): + + self.cancelled = False + self.status = NO_STATUS + self.thread_install_update = None super(UpdateInstallerDialog, self).__init__(parent) self.setStyleSheet( f"background-color: {QStylePalette.COLOR_BACKGROUND_2}") self.setWindowFlags(Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) self._parent = parent - self._installation_thread = installation_thread self._installation_widget = UpdateInstallation(self) # Layout @@ -108,11 +126,11 @@ def __init__(self, parent, installation_thread): self.setLayout(installer_layout) # Signals - self._installation_thread.sig_download_progress.connect( + self.sig_download_progress.connect( self._installation_widget.update_installation_progress) - self._installation_thread.sig_installation_status.connect( + self.sig_installation_status.connect( self._installation_widget.update_installation_status) - self._installation_thread.sig_installation_status.connect( + self.sig_installation_status.connect( self.finished_installation) self._installation_widget.ok_button.clicked.connect( @@ -135,8 +153,8 @@ def cancel_install(self): _('Do you really want to cancel Update installation?'), QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: - self._installation_thread.cancelled = True - self._installation_thread.cancell_thread_install_update() + self.cancelled = True + self.cancell_thread_install_update() self.setup() self.accept() return True @@ -147,13 +165,13 @@ def continue_install(self): reply = QMessageBox(icon=QMessageBox.Question, text=_('Do you want to download and' ' install the latest version of' - 'spyder?
'), + ' spyder?
'), parent=self._parent) reply.setWindowTitle("Spyder") reply.setAttribute(Qt.WA_ShowWithoutActivating) reply.setStandardButtons(QMessageBox.Yes | QMessageBox.No) reply.buttonClicked.connect( - self._installation_thread.installation_thread) + self.start_installation_update) reply.show() def finished_installation(self, status): @@ -164,8 +182,8 @@ def finished_installation(self, status): def close_installer(self): """Close the installation dialog.""" - if (self._installation_thread.status == FINISHED - or self._installation_thread.status == CANCELLED): + if (self.status == FINISHED + or self.status == CANCELLED): self.setup() self.accept() else: @@ -178,3 +196,55 @@ def reject(self): self.close_installer() else: super(UpdateInstallerDialog, self).reject() + + def _change_update_installation_status(self, status=NO_STATUS): + """Set the installation status.""" + self.status = status + self.sig_installation_status.emit(self.status) + + def _progress_reporter(self, block_number, read_size, total_size): + progress = 0 + if total_size > 0: + progress = block_number * read_size + if self.cancelled: + raise UpdateInstallationCancelledException() + else: + self.sig_download_progress.emit(progress, total_size) + + def cancell_thread_install_update(self): + self._change_update_installation_status(status=CANCELLED) + self.thread_install_update.join() + + def _download_install(self): + try: + with TemporaryDirectory(prefix="Spyder-") as tmpdir: + destination = os.path.join(tmpdir, 'updateSpyder.exe') + download = urlretrieve( + ('https://github.com/spyder-ide/spyder/releases/latest/' + 'download/Spyder_64bit_full.exe'), + destination, + reporthook=self._progress_reporter) + self._change_update_installation_status(status=INSTALLING) + install = subprocess.Popen(destination, shell=True) + install.communicate() + except UpdateInstallationCancelledException: + self._change_update_installation_status(status=CANCELLED) + finally: + self._change_update_installation_status(status=PENDING) + + def start_installation_update(self, option): + if option.text() in ('&Yes'): + self.cancelled = False + self._change_update_installation_status( + status=DOWNLOADING_INSTALLER) + """call a function in a simple thread, to prevent blocking""" + self.thread_install_update = threading.Thread( + target=self._download_install) + self.thread_install_update.start() + else: + self._change_update_installation_status(status=PENDING) + + +class UpdateInstallationCancelledException(Exception): + """Update installation was cancelled.""" + pass diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 3a40b09a446..f48fdded9a5 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -42,19 +42,22 @@ class ApplicationUpdateStatus(StatusBarWidget): def __init__(self, parent): + self.cancelled = False + self.status = NO_STATUS + self.thread_install_update = None self.tooltip = self.BASE_TOOLTIP - self.installation_thread = parent + self._container = parent super().__init__(parent) # Installation dialog self.installer = UpdateInstallerDialog( - self, - self.installation_thread) + self) - self.installation_thread.sig_installation_status.connect( - self.set_value) self.sig_clicked.connect(self.show_installation_dialog) + self.installer.sig_installation_status.connect( + self.set_value) + def set_value(self, value): """Return update installation state.""" versions = get_versions() @@ -82,6 +85,9 @@ def get_tooltip(self): def get_icon(self): return ima.icon('spyder') + + def start_installation(self): + self.installer.start_installation_update() @Slot() def show_installation_dialog(self): From fb083bd240346eba6ffa54c09d301acd33ebe6de Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 9 Aug 2022 14:11:51 -0500 Subject: [PATCH 13/49] WIP check updates --- spyder/plugins/application/container.py | 21 +++++---------- spyder/plugins/application/widgets/status.py | 28 +++++++++----------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 1575377391d..d2ecbd08719 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -45,16 +45,6 @@ _ = get_translation('spyder') -# Update Installation process statuses -NO_STATUS = _("No status") -DOWNLOADING_INSTALLER = _("Downloading installer") -INSTALLING = _("Installing") -FINISHED = _("Installation finished") -PENDING = _("Pending update") -CHECKING = _("Checking for updates") -CANCELLED = _("Cancelled") - - class ApplicationPluginMenus: DebugLogsMenu = "debug_logs_menu" @@ -116,7 +106,7 @@ def __init__(self, name, plugin, parent=None): def setup(self): self.application_update_status = ApplicationUpdateStatus(parent=self) - self.application_update_status.set_value(NO_STATUS) + self.application_update_status.set_no_status() # Compute dependencies in a thread to not block the interface. self.dependencies_thread = QThread(None) @@ -301,7 +291,7 @@ def _check_updates_ready(self): else: if update_available: - self.application_update_status.set_value(PENDING) + self.application_update_status.set_status_pending() header = _("Spyder {} is available!

").format( latest_release) @@ -320,7 +310,7 @@ def _check_updates_ready(self): "conda update anaconda
" "conda install spyder={}

" ).format(latest_release) - self.application_update_status.set_value(NO_STATUS) + self.application_update_status.set_no_status() else: if os.name == 'nt' and is_pynsist(): box.setStandardButtons(QMessageBox.Yes | @@ -346,6 +336,7 @@ def _check_updates_ready(self): box.set_check_visible(False) box.exec_() check_updates = box.is_checked() + self.application_update_status.set_no_status() # Update checkbox based on user interaction self.set_conf(option, check_updates) @@ -362,9 +353,9 @@ def check_updates(self, startup=False): # Disable check_updates_action while the thread is working self.check_updates_action.setDisabled(True) if os.name == 'nt': - self.application_update_status.set_value(CHECKING) + self.application_update_status.set_status_checking() else: - self.application_update_status.set_value(NO_STATUS) + self.application_update_status.set_no_status() if self.thread_updates is not None: self.thread_updates.quit() diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index f48fdded9a5..c3cf2ddae89 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -18,23 +18,13 @@ from spyder.api.widgets.status import StatusBarWidget from spyder.config.base import _ from spyder.plugins.application.widgets.install import ( - UpdateInstallerDialog) + UpdateInstallerDialog, NO_STATUS, DOWNLOADING_INSTALLER, INSTALLING, FINISHED, PENDING, CHECKING, CANCELLED) from spyder.utils.icon_manager import ima from spyder import get_versions logger = logging.getLogger(__name__) -# Update Installation process statusesf -NO_STATUS = _("No status") -DOWNLOADING_INSTALLER = _("Downloading installer") -INSTALLING = _("Installing") -FINISHED = _("Installation finished") -PENDING = _("Pending update") -CHECKING = _("Checking for updates") -CANCELLED = _("Cancelled") - - class ApplicationUpdateStatus(StatusBarWidget): """Status bar widget for Application update status.""" BASE_TOOLTIP = _("Application update status") @@ -62,19 +52,16 @@ def set_value(self, value): """Return update installation state.""" versions = get_versions() if value == DOWNLOADING_INSTALLER or value == INSTALLING: - self.setVisible(True) self.tooltip = _("Update installation will continue in the " "background.\n" "Click here to show the installation " "dialog again") - elif value == CHECKING or value == PENDING: - self.setVisible(True) + elif value == PENDING: self.tooltip = value else: - self.setVisible(False) value = versions['spyder'] self.tooltip = self.BASE_TOOLTIP - + self.setVisible(True) self.update_tooltip() value = "Spyder: {0}".format(value) super().set_value(value) @@ -89,6 +76,15 @@ def get_icon(self): def start_installation(self): self.installer.start_installation_update() + def set_status_pending(self): + self.set_value(PENDING) + + def set_status_checking(self): + self.set_value(CHECKING) + + def set_no_status(self): + self.set_value(NO_STATUS) + @Slot() def show_installation_dialog(self): """Show installation dialog.""" From bb5443764e84287c9f56ef1fdb1e68fc505b57e8 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Wed, 10 Aug 2022 11:15:09 -0500 Subject: [PATCH 14/49] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/container.py | 10 +--------- spyder/plugins/application/widgets/install.py | 9 +++++---- spyder/plugins/application/widgets/status.py | 1 - 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index d2ecbd08719..d771c691051 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -310,7 +310,6 @@ def _check_updates_ready(self): "conda update anaconda
" "conda install spyder={}

" ).format(latest_release) - self.application_update_status.set_no_status() else: if os.name == 'nt' and is_pynsist(): box.setStandardButtons(QMessageBox.Yes | @@ -352,10 +351,7 @@ def check_updates(self, startup=False): """Check for spyder updates on github releases using a QThread.""" # Disable check_updates_action while the thread is working self.check_updates_action.setDisabled(True) - if os.name == 'nt': - self.application_update_status.set_status_checking() - else: - self.application_update_status.set_no_status() + self.application_update_status.set_status_checking() if self.thread_updates is not None: self.thread_updates.quit() @@ -626,7 +622,3 @@ def show_dpi_change_message(self, dpi): self.dpi_messagebox.move(int(x), int(y)) self.dpi_messagebox.adjustSize() - -class UpdateInstallationCancelledException(Exception): - """Update installation was cancelled.""" - pass diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index f6210b5c46f..93cc5cd4b12 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -20,13 +20,14 @@ QWidget) # Local imports +from spyder import __version__ from spyder.config.base import _ from spyder.utils.icon_manager import ima from spyder.utils.palette import QStylePalette # Update installation process statuses -NO_STATUS = _("No status") +NO_STATUS = __version__ DOWNLOADING_INSTALLER = _("Downloading installer") INSTALLING = _("Installing") FINISHED = _("Installation finished") @@ -57,7 +58,7 @@ def __init__(self, parent): self._progress_label = QLabel(_('Downloading')) self.install_info = QLabel( - _("Dowloading the latest Spyder version
")) + _("Downloading the latest Spyder version
")) button_layout = QHBoxLayout() self.ok_button = QPushButton(_('OK')) @@ -150,7 +151,7 @@ def cancel_install(self): """Cancel the installation in progress.""" reply = QMessageBox.critical( self._parent, 'Spyder', - _('Do you really want to cancel Update installation?'), + _('Do you really want to cancel installing the Spyder update?'), QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self.cancelled = True @@ -161,7 +162,7 @@ def cancel_install(self): return False def continue_install(self): - """Cancel the installation in progress.""" + """Continue the installation in progress by downloading the installer.""" reply = QMessageBox(icon=QMessageBox.Question, text=_('Do you want to download and' ' install the latest version of' diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index c3cf2ddae89..9f98ba244f6 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -59,7 +59,6 @@ def set_value(self, value): elif value == PENDING: self.tooltip = value else: - value = versions['spyder'] self.tooltip = self.BASE_TOOLTIP self.setVisible(True) self.update_tooltip() From 150954dffe2b526af4f4546b3711d4d5c247b00b Mon Sep 17 00:00:00 2001 From: jsbautista Date: Fri, 12 Aug 2022 13:44:31 -0500 Subject: [PATCH 15/49] WIP check updates --- spyder/plugins/application/container.py | 11 +++++-- spyder/plugins/application/widgets/install.py | 31 ++++++++++--------- spyder/plugins/application/widgets/status.py | 5 +-- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index d771c691051..2320184ff97 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -96,7 +96,7 @@ class ApplicationContainer(PluginMainContainer): def __init__(self, name, plugin, parent=None): super().__init__(name, plugin, parent) - + # Keep track of dpi message self.current_dpi = None self.dpi_messagebox = None @@ -318,7 +318,13 @@ def _check_updates_ready(self): "You want to download and install the latest " "version of spyder?

" ) - box.buttonClicked.connect(self.application_update_status.start_installation) + box.buttonClicked.connect( + lambda button: + self.application_update_status.start_installation() + if button.text() in ('&Yes') + else + self.application_update_status.set_status_pending() + ) else: content = _( "Click this link to " @@ -621,4 +627,3 @@ def show_dpi_change_message(self, dpi): # Fixes spyder-ide/spyder#17677 self.dpi_messagebox.move(int(x), int(y)) self.dpi_messagebox.adjustSize() - diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 93cc5cd4b12..7c0acdd33ed 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -8,6 +8,7 @@ # Standard library imports import sys +import os import subprocess from urllib.request import urlretrieve from tempfile import TemporaryDirectory @@ -65,7 +66,6 @@ def __init__(self, parent): button_layout.addStretch() button_layout.addWidget(self.ok_button) button_layout.addStretch() - action_layout.addStretch() action_layout.addWidget(self._progress_label) action_layout.addWidget(self._progress_widget) @@ -107,7 +107,6 @@ class UpdateInstallerDialog(QDialog): # str: Status string sig_installation_status = Signal(str) - def __init__(self, parent): self.cancelled = False @@ -162,7 +161,8 @@ def cancel_install(self): return False def continue_install(self): - """Continue the installation in progress by downloading the installer.""" + """Continue the installation in progress + by downloading the installer.""" reply = QMessageBox(icon=QMessageBox.Question, text=_('Do you want to download and' ' install the latest version of' @@ -172,7 +172,11 @@ def continue_install(self): reply.setAttribute(Qt.WA_ShowWithoutActivating) reply.setStandardButtons(QMessageBox.Yes | QMessageBox.No) reply.buttonClicked.connect( - self.start_installation_update) + lambda button: + self.start_installation_update() + if button.text() in ('&Yes') + else self._change_update_installation_status( + status=PENDING)) reply.show() def finished_installation(self, status): @@ -233,17 +237,14 @@ def _download_install(self): finally: self._change_update_installation_status(status=PENDING) - def start_installation_update(self, option): - if option.text() in ('&Yes'): - self.cancelled = False - self._change_update_installation_status( - status=DOWNLOADING_INSTALLER) - """call a function in a simple thread, to prevent blocking""" - self.thread_install_update = threading.Thread( - target=self._download_install) - self.thread_install_update.start() - else: - self._change_update_installation_status(status=PENDING) + def start_installation_update(self): + self.cancelled = False + self._change_update_installation_status( + status=DOWNLOADING_INSTALLER) + """call a function in a simple thread, to prevent blocking""" + self.thread_install_update = threading.Thread( + target=self._download_install) + self.thread_install_update.start() class UpdateInstallationCancelledException(Exception): diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 9f98ba244f6..19631d8ed2f 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -18,7 +18,8 @@ from spyder.api.widgets.status import StatusBarWidget from spyder.config.base import _ from spyder.plugins.application.widgets.install import ( - UpdateInstallerDialog, NO_STATUS, DOWNLOADING_INSTALLER, INSTALLING, FINISHED, PENDING, CHECKING, CANCELLED) + UpdateInstallerDialog, NO_STATUS, DOWNLOADING_INSTALLER, INSTALLING, + FINISHED, PENDING, CHECKING, CANCELLED) from spyder.utils.icon_manager import ima from spyder import get_versions @@ -71,7 +72,7 @@ def get_tooltip(self): def get_icon(self): return ima.icon('spyder') - + def start_installation(self): self.installer.start_installation_update() From 0334291d67f987454b3c5313011b624dd3b2f996 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Mon, 15 Aug 2022 21:07:03 -0500 Subject: [PATCH 16/49] WIP check updates --- spyder/plugins/application/container.py | 14 ++++++-------- spyder/plugins/application/widgets/install.py | 11 +++++------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 2320184ff97..125da4ba6bd 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -310,6 +310,7 @@ def _check_updates_ready(self): "conda update anaconda
" "conda install spyder={}

" ).format(latest_release) + self.application_update_status.set_no_status() else: if os.name == 'nt' and is_pynsist(): box.setStandardButtons(QMessageBox.Yes | @@ -318,13 +319,6 @@ def _check_updates_ready(self): "You want to download and install the latest " "version of spyder?

" ) - box.buttonClicked.connect( - lambda button: - self.application_update_status.start_installation() - if button.text() in ('&Yes') - else - self.application_update_status.set_status_pending() - ) else: content = _( "Click this link to " @@ -333,7 +327,11 @@ def _check_updates_ready(self): msg = header + content + footer box.setText(msg) box.set_check_visible(True) - box.show() + box.exec_() + if box.result() == QMessageBox.Yes: + self.application_update_status.start_installation() + elif(box.result() == QMessageBox.No): + self.application_update_status.set_status_pending() check_updates = box.is_checked() elif feedback: msg = _("Spyder is up to date.") diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 7c0acdd33ed..bf8c4f13450 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -171,13 +171,12 @@ def continue_install(self): reply.setWindowTitle("Spyder") reply.setAttribute(Qt.WA_ShowWithoutActivating) reply.setStandardButtons(QMessageBox.Yes | QMessageBox.No) - reply.buttonClicked.connect( - lambda button: - self.start_installation_update() - if button.text() in ('&Yes') - else self._change_update_installation_status( - status=PENDING)) reply.show() + reply.exec_() + if reply.result() == QMessageBox.Yes: + self.start_installation_update() + else: + self._change_update_installation_status(status=PENDING) def finished_installation(self, status): """Handle finished installation.""" From efbf36819ee94a10c5674b184c568473ca3cfeb4 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Mon, 15 Aug 2022 21:19:41 -0500 Subject: [PATCH 17/49] WIP check updates --- spyder/plugins/application/widgets/install.py | 1 - 1 file changed, 1 deletion(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index bf8c4f13450..edf60cbb23a 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -171,7 +171,6 @@ def continue_install(self): reply.setWindowTitle("Spyder") reply.setAttribute(Qt.WA_ShowWithoutActivating) reply.setStandardButtons(QMessageBox.Yes | QMessageBox.No) - reply.show() reply.exec_() if reply.result() == QMessageBox.Yes: self.start_installation_update() From 662dd625b030c5e96ce27b205a4d3a147b4b0bb9 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 16 Aug 2022 13:29:09 -0500 Subject: [PATCH 18/49] WIP check updates --- spyder/plugins/application/widgets/install.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index edf60cbb23a..0c889869880 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -25,6 +25,7 @@ from spyder.config.base import _ from spyder.utils.icon_manager import ima from spyder.utils.palette import QStylePalette +from spyder.utils.programs import is_module_installed # Update installation process statuses @@ -222,11 +223,18 @@ def _download_install(self): try: with TemporaryDirectory(prefix="Spyder-") as tmpdir: destination = os.path.join(tmpdir, 'updateSpyder.exe') - download = urlretrieve( - ('https://github.com/spyder-ide/spyder/releases/latest/' - 'download/Spyder_64bit_full.exe'), - destination, - reporthook=self._progress_reporter) + url = ( + 'https://github.com/spyder-ide/spyder/releases/latest/' + 'download/Spyder_64bit_lite.exe') + val = (is_module_installed('numpy') or + is_module_installed('pandas')) + if val: + url = ( + 'https://github.com/spyder-ide/spyder/releases/latest/' + 'download/Spyder_64bit_full.exe') + download = urlretrieve(url, + destination, + reporthook=self._progress_reporter) self._change_update_installation_status(status=INSTALLING) install = subprocess.Popen(destination, shell=True) install.communicate() From ed20f59871be3e7a88428f4586ae2a34afaafab2 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 16 Aug 2022 19:01:53 -0500 Subject: [PATCH 19/49] WIP check updates --- spyder/plugins/application/container.py | 1 - spyder/plugins/application/widgets/status.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 125da4ba6bd..1e091cc03f7 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -310,7 +310,6 @@ def _check_updates_ready(self): "conda update anaconda
" "conda install spyder={}

" ).format(latest_release) - self.application_update_status.set_no_status() else: if os.name == 'nt' and is_pynsist(): box.setStandardButtons(QMessageBox.Yes | diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 19631d8ed2f..b5b8d45f8f4 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -16,7 +16,7 @@ # Local imports from spyder.api.widgets.status import StatusBarWidget -from spyder.config.base import _ +from spyder.config.base import _, is_pynsist from spyder.plugins.application.widgets.install import ( UpdateInstallerDialog, NO_STATUS, DOWNLOADING_INSTALLER, INSTALLING, FINISHED, PENDING, CHECKING, CANCELLED) @@ -88,8 +88,8 @@ def set_no_status(self): @Slot() def show_installation_dialog(self): """Show installation dialog.""" - if (not self.tooltip == self.BASE_TOOLTIP and not - self.tooltip == PENDING): + if ((not self.tooltip == self.BASE_TOOLTIP and not + self.tooltip == PENDING) and is_pynsist): self.installer.show() - elif (self.tooltip == PENDING): + elif ((self.tooltip == PENDING) and is_pynsist): self.installer.continue_install() From d92f7c3b774e92036602c39e1791e4f2acc1f528 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Tue, 16 Aug 2022 23:03:00 -0500 Subject: [PATCH 20/49] Apply suggestions from code review Co-authored-by: Carlos Cordoba --- spyder/plugins/application/container.py | 10 +++--- spyder/plugins/application/plugin.py | 2 +- .../plugins/application/widgets/__init__.py | 2 +- spyder/plugins/application/widgets/install.py | 32 ++++++++++--------- spyder/plugins/application/widgets/status.py | 7 ++-- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 1e091cc03f7..02d90457883 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -28,13 +28,13 @@ from spyder.utils.installers import InstallerMissingDependencies from spyder.config.utils import is_anaconda from spyder.config.base import get_conf_path, get_debug_level, is_pynsist +from spyder.plugins.application.widgets.status import ApplicationUpdateStatus from spyder.plugins.console.api import ConsoleActions from spyder.utils.qthelpers import start_file, DialogManager from spyder.widgets.about import AboutDialog from spyder.widgets.dependencies import DependenciesDialog from spyder.widgets.helperwidgets import MessageCheckBox from spyder.workers.updates import WorkerUpdates -from spyder.plugins.application.widgets.status import ApplicationUpdateStatus WinUserEnvDialog = None @@ -89,6 +89,7 @@ class ApplicationContainer(PluginMainContainer): """ Signal to load a log file """ + sig_show_status_bar_widget = Signal(bool) """ Signal to show the widget in status bar @@ -107,6 +108,7 @@ def setup(self): self.application_update_status = ApplicationUpdateStatus(parent=self) self.application_update_status.set_no_status() + # Compute dependencies in a thread to not block the interface. self.dependencies_thread = QThread(None) @@ -245,7 +247,6 @@ def show_windows_env_variables(self): # ------------------------------------------------------------------------- def _check_updates_ready(self): - """Show results of the Spyder update checking process.""" # `feedback` = False is used on startup, so only positive feedback is @@ -254,7 +255,6 @@ def _check_updates_ready(self): feedback = self.give_updates_feedback # Get results from worker - update_available = self.worker_updates.update_available latest_release = self.worker_updates.latest_release error_msg = self.worker_updates.error @@ -316,17 +316,19 @@ def _check_updates_ready(self): QMessageBox.No) content = _( "You want to download and install the latest " - "version of spyder?

" + "version of Spyder?

" ) else: content = _( "Click this link to " "download it.

" ).format(url_r) + msg = header + content + footer box.setText(msg) box.set_check_visible(True) box.exec_() + if box.result() == QMessageBox.Yes: self.application_update_status.start_installation() elif(box.result() == QMessageBox.No): diff --git a/spyder/plugins/application/plugin.py b/spyder/plugins/application/plugin.py index 11a534bf1b8..ed0a95b28d3 100644 --- a/spyder/plugins/application/plugin.py +++ b/spyder/plugins/application/plugin.py @@ -111,7 +111,7 @@ def on_statusbar_available(self): @on_plugin_teardown(plugin=Plugins.StatusBar) def on_statusbar_teardown(self): - # Add status widget + # Remove status widget statusbar = self.get_plugin(Plugins.StatusBar) statusbar.remove_status_widget(self.application_update_status.ID) diff --git a/spyder/plugins/application/widgets/__init__.py b/spyder/plugins/application/widgets/__init__.py index 8b6d0e5dda2..5a3f944db10 100644 --- a/spyder/plugins/application/widgets/__init__.py +++ b/spyder/plugins/application/widgets/__init__.py @@ -5,4 +5,4 @@ # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) -"""Widgets for the application plugin.""" +"""Widgets for the Application plugin.""" diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 0c889869880..a3a75809eae 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -4,15 +4,15 @@ # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) -"""Update installation widget.""" +"""Update installation widgets.""" # Standard library imports -import sys import os import subprocess -from urllib.request import urlretrieve +import sys from tempfile import TemporaryDirectory import threading +from urllib.request import urlretrieve # Third-party imports from qtpy.QtCore import QEvent, QObject, Qt, Signal @@ -22,12 +22,15 @@ # Local imports from spyder import __version__ -from spyder.config.base import _ +from spyder.api.translations import get_translation from spyder.utils.icon_manager import ima from spyder.utils.palette import QStylePalette from spyder.utils.programs import is_module_installed +# Localization +_ = get_translation('spyder') + # Update installation process statuses NO_STATUS = __version__ DOWNLOADING_INSTALLER = _("Downloading installer") @@ -42,7 +45,7 @@ class UpdateInstallation(QWidget): """Update progress installation widget.""" def __init__(self, parent): - super(UpdateInstallation, self).__init__(parent) + super().__init__(parent) # Left side action_layout = QVBoxLayout() @@ -96,14 +99,13 @@ def update_installation_progress(self, current_value, total): class UpdateInstallerDialog(QDialog): - """Spyder installer.""" + """Update installer dialog.""" # Signal to get the download progress # int: Download progress # int: Total download size sig_download_progress = Signal(int, int) - # Signals # Signal to get the current status of the update installation # str: Status string sig_installation_status = Signal(str) @@ -113,7 +115,7 @@ def __init__(self, parent): self.cancelled = False self.status = NO_STATUS self.thread_install_update = None - super(UpdateInstallerDialog, self).__init__(parent) + super().__init__(parent) self.setStyleSheet( f"background-color: {QStylePalette.COLOR_BACKGROUND_2}") self.setWindowFlags(Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) @@ -123,7 +125,6 @@ def __init__(self, parent): # Layout installer_layout = QVBoxLayout() installer_layout.addWidget(self._installation_widget) - self.setLayout(installer_layout) # Signals @@ -151,7 +152,7 @@ def cancel_install(self): """Cancel the installation in progress.""" reply = QMessageBox.critical( self._parent, 'Spyder', - _('Do you really want to cancel installing the Spyder update?'), + _('Do you really want to cancel the Spyder update installation?'), QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self.cancelled = True @@ -162,12 +163,13 @@ def cancel_install(self): return False def continue_install(self): - """Continue the installation in progress - by downloading the installer.""" + """ + Continue the installation in progress by downloading the installer. + """ reply = QMessageBox(icon=QMessageBox.Question, text=_('Do you want to download and' ' install the latest version of' - ' spyder?
'), + ' Spyder?
'), parent=self._parent) reply.setWindowTitle("Spyder") reply.setAttribute(Qt.WA_ShowWithoutActivating) @@ -194,7 +196,7 @@ def close_installer(self): self.hide() def reject(self): - """Reimplement Qt method.""" + """Reimplemented Qt method.""" on_installation_widget = self._installation_widget.isVisible() if on_installation_widget: self.close_installer() @@ -215,7 +217,7 @@ def _progress_reporter(self, block_number, read_size, total_size): else: self.sig_download_progress.emit(progress, total_size) - def cancell_thread_install_update(self): + def cancel_thread_install_update(self): self._change_update_installation_status(status=CANCELLED) self.thread_install_update.join() diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index b5b8d45f8f4..3a6c8fd0f4e 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -15,13 +15,13 @@ from qtpy.QtCore import Slot # Local imports +from spyder import get_versions from spyder.api.widgets.status import StatusBarWidget from spyder.config.base import _, is_pynsist from spyder.plugins.application.widgets.install import ( UpdateInstallerDialog, NO_STATUS, DOWNLOADING_INSTALLER, INSTALLING, FINISHED, PENDING, CHECKING, CANCELLED) from spyder.utils.icon_manager import ima -from spyder import get_versions logger = logging.getLogger(__name__) @@ -41,8 +41,7 @@ def __init__(self, parent): super().__init__(parent) # Installation dialog - self.installer = UpdateInstallerDialog( - self) + self.installer = UpdateInstallerDialog(self) self.sig_clicked.connect(self.show_installation_dialog) @@ -71,7 +70,7 @@ def get_tooltip(self): return self.tooltip def get_icon(self): - return ima.icon('spyder') + return ima.icon('spyder_about') def start_installation(self): self.installer.start_installation_update() From 4349394d87f4895556177e7c3cda44caace4076c Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Wed, 17 Aug 2022 12:14:58 -0500 Subject: [PATCH 21/49] Apply suggestions from code review Co-authored-by: Carlos Cordoba --- spyder/plugins/application/widgets/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index a3a75809eae..6e6d68f2f85 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -156,7 +156,7 @@ def cancel_install(self): QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self.cancelled = True - self.cancell_thread_install_update() + self.cancel_thread_install_update() self.setup() self.accept() return True From 6fd2b2792497d4b2911bd7329e48c8c24e75c12a Mon Sep 17 00:00:00 2001 From: jsbautista Date: Wed, 17 Aug 2022 12:19:05 -0500 Subject: [PATCH 22/49] WIP check updates --- spyder/plugins/application/widgets/install.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 6e6d68f2f85..2d0e2518052 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -41,6 +41,11 @@ CANCELLED = _("Cancelled") +class UpdateInstallationCancelledException(Exception): + """Update installation was cancelled.""" + pass + + class UpdateInstallation(QWidget): """Update progress installation widget.""" @@ -253,8 +258,3 @@ def start_installation_update(self): self.thread_install_update = threading.Thread( target=self._download_install) self.thread_install_update.start() - - -class UpdateInstallationCancelledException(Exception): - """Update installation was cancelled.""" - pass From 201ee4be69f90dbd88c173682f4b7d9d5df5afda Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Wed, 17 Aug 2022 21:55:19 -0500 Subject: [PATCH 23/49] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/widgets/install.py | 6 +++--- spyder/plugins/application/widgets/status.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 2d0e2518052..996e3af1c6a 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -233,9 +233,9 @@ def _download_install(self): url = ( 'https://github.com/spyder-ide/spyder/releases/latest/' 'download/Spyder_64bit_lite.exe') - val = (is_module_installed('numpy') or - is_module_installed('pandas')) - if val: + is_full_installer = (is_module_installed('numpy') or + is_module_installed('pandas')) + if is_full_installer: url = ( 'https://github.com/spyder-ide/spyder/releases/latest/' 'download/Spyder_64bit_full.exe') diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 3a6c8fd0f4e..3b1969a4c14 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -62,7 +62,8 @@ def set_value(self, value): self.tooltip = self.BASE_TOOLTIP self.setVisible(True) self.update_tooltip() - value = "Spyder: {0}".format(value) + value = f"Spyder: {value}" + logger.debug(f"Application Update Status: {value}") super().set_value(value) def get_tooltip(self): From d99b2447376adaeadd36795158d0de0ddfcb6b5e Mon Sep 17 00:00:00 2001 From: jsbautista Date: Wed, 17 Aug 2022 23:59:31 -0500 Subject: [PATCH 24/49] WIP check updates --- spyder/plugins/application/widgets/install.py | 5 +++++ spyder/plugins/application/widgets/status.py | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 996e3af1c6a..4e65a084767 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -7,6 +7,7 @@ """Update installation widgets.""" # Standard library imports +import logging import os import subprocess import sys @@ -27,6 +28,7 @@ from spyder.utils.palette import QStylePalette from spyder.utils.programs import is_module_installed +logger = logging.getLogger(__name__) # Localization _ = get_translation('spyder') @@ -210,6 +212,7 @@ def reject(self): def _change_update_installation_status(self, status=NO_STATUS): """Set the installation status.""" + logger.debug(f"Installation status: {status}") self.status = status self.sig_installation_status.emit(self.status) @@ -228,6 +231,7 @@ def cancel_thread_install_update(self): def _download_install(self): try: + logger.debug("Downloading installer executable") with TemporaryDirectory(prefix="Spyder-") as tmpdir: destination = os.path.join(tmpdir, 'updateSpyder.exe') url = ( @@ -239,6 +243,7 @@ def _download_install(self): url = ( 'https://github.com/spyder-ide/spyder/releases/latest/' 'download/Spyder_64bit_full.exe') + logger.debug(f"Downloading installer from: {url}") download = urlretrieve(url, destination, reporthook=self._progress_reporter) diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 3b1969a4c14..5a0c1c541ad 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -33,9 +33,6 @@ class ApplicationUpdateStatus(StatusBarWidget): def __init__(self, parent): - self.cancelled = False - self.status = NO_STATUS - self.thread_install_update = None self.tooltip = self.BASE_TOOLTIP self._container = parent super().__init__(parent) From 0cb460ec8da6315efba836a0dc0adf89575f3fb5 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Thu, 18 Aug 2022 20:51:14 -0500 Subject: [PATCH 25/49] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/widgets/install.py | 4 +--- spyder/plugins/application/widgets/status.py | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 4e65a084767..8cbaecfe6ac 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -123,8 +123,6 @@ def __init__(self, parent): self.status = NO_STATUS self.thread_install_update = None super().__init__(parent) - self.setStyleSheet( - f"background-color: {QStylePalette.COLOR_BACKGROUND_2}") self.setWindowFlags(Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) self._parent = parent self._installation_widget = UpdateInstallation(self) @@ -256,10 +254,10 @@ def _download_install(self): self._change_update_installation_status(status=PENDING) def start_installation_update(self): + """Start the installation update thread and set downloading status.""" self.cancelled = False self._change_update_installation_status( status=DOWNLOADING_INSTALLER) - """call a function in a simple thread, to prevent blocking""" self.thread_install_update = threading.Thread( target=self._download_install) self.thread_install_update.start() diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 5a0c1c541ad..729585a51ac 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -34,8 +34,7 @@ class ApplicationUpdateStatus(StatusBarWidget): def __init__(self, parent): self.tooltip = self.BASE_TOOLTIP - self._container = parent - super().__init__(parent) + super().__init__(parent, show_spinner=True) # Installation dialog self.installer = UpdateInstallerDialog(self) @@ -47,14 +46,17 @@ def __init__(self, parent): def set_value(self, value): """Return update installation state.""" - versions = get_versions() if value == DOWNLOADING_INSTALLER or value == INSTALLING: self.tooltip = _("Update installation will continue in the " "background.\n" "Click here to show the installation " "dialog again") + self.spinner.show() + self.spinner.start() elif value == PENDING: self.tooltip = value + self.spinner.hide() + self.spinner.stop() else: self.tooltip = self.BASE_TOOLTIP self.setVisible(True) @@ -75,18 +77,24 @@ def start_installation(self): def set_status_pending(self): self.set_value(PENDING) + self.spinner.hide() + self.spinner.stop() def set_status_checking(self): self.set_value(CHECKING) + self.spinner.show() + self.spinner.start() def set_no_status(self): self.set_value(NO_STATUS) + self.spinner.hide() + self.spinner.stop() @Slot() def show_installation_dialog(self): """Show installation dialog.""" if ((not self.tooltip == self.BASE_TOOLTIP and not - self.tooltip == PENDING) and is_pynsist): + self.tooltip == PENDING) and is_pynsist()): self.installer.show() - elif ((self.tooltip == PENDING) and is_pynsist): + elif ((self.tooltip == PENDING) and is_pynsist()): self.installer.continue_install() From 22d31b69cfc7e08370e3be4842f5418836f35a74 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Mon, 22 Aug 2022 13:35:25 -0500 Subject: [PATCH 26/49] WIP check updates --- spyder/plugins/application/container.py | 9 +-- spyder/plugins/application/widgets/install.py | 56 +++++++++++-------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 02d90457883..6cff3127d82 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -311,12 +311,12 @@ def _check_updates_ready(self): "conda install spyder={}

" ).format(latest_release) else: - if os.name == 'nt' and is_pynsist(): + if is_pynsist(): box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) content = _( - "You want to download and install the latest " - "version of Spyder?

" + "Would you like to automatically download and" + " install the latest version of Spyder?

" ) else: content = _( @@ -341,7 +341,8 @@ def _check_updates_ready(self): box.exec_() check_updates = box.is_checked() self.application_update_status.set_no_status() - + else: + self.application_update_status.set_no_status() # Update checkbox based on user interaction self.set_conf(option, check_updates) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 8cbaecfe6ac..8d92081b877 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -10,13 +10,12 @@ import logging import os import subprocess -import sys -from tempfile import TemporaryDirectory +import tempfile import threading from urllib.request import urlretrieve # Third-party imports -from qtpy.QtCore import QEvent, QObject, Qt, Signal +from qtpy.QtCore import Qt, Signal from qtpy.QtWidgets import (QDialog, QHBoxLayout, QMessageBox, QLabel, QProgressBar, QPushButton, QVBoxLayout, QWidget) @@ -25,7 +24,6 @@ from spyder import __version__ from spyder.api.translations import get_translation from spyder.utils.icon_manager import ima -from spyder.utils.palette import QStylePalette from spyder.utils.programs import is_module_installed logger = logging.getLogger(__name__) @@ -42,6 +40,16 @@ CHECKING = _("Checking for updates") CANCELLED = _("Cancelled") +INSTALL_INFO_MESSAGES = { + DOWNLOADING_INSTALLER: _("Downloading Spyder latest " + "release installer executable"), + INSTALLING: _("Installing Spyder latest release"), + FINISHED: _("Spyder update installation finished"), + PENDING: _("An update is pending"), + CHECKING: _("Checking for updates"), + CANCELLED: _("Update cancelled") +} + class UpdateInstallationCancelledException(Exception): """Update installation was cancelled.""" @@ -94,7 +102,7 @@ def __init__(self, parent): def update_installation_status(self, status): """Update installation status (downloading, installing, finished).""" self._progress_label.setText(status) - self.install_info.setText(status + _(" the latest version of Spyder.")) + self.install_info.setText(INSTALL_INFO_MESSAGES[status]) if status == INSTALLING: self._progress_bar.setRange(0, 0) self.cancel_button.hide() @@ -172,9 +180,9 @@ def continue_install(self): Continue the installation in progress by downloading the installer. """ reply = QMessageBox(icon=QMessageBox.Question, - text=_('Do you want to download and' - ' install the latest version of' - ' Spyder?
'), + text=_("Would you like to automatically download a" + "nd install the latest version of Spyder?" + "

"), parent=self._parent) reply.setWindowTitle("Spyder") reply.setAttribute(Qt.WA_ShowWithoutActivating) @@ -230,24 +238,24 @@ def cancel_thread_install_update(self): def _download_install(self): try: logger.debug("Downloading installer executable") - with TemporaryDirectory(prefix="Spyder-") as tmpdir: - destination = os.path.join(tmpdir, 'updateSpyder.exe') + tmpdir = tempfile.mkdtemp(prefix="Spyder-") + destination = os.path.join(tmpdir, 'updateSpyder.exe') + url = ( + 'https://github.com/spyder-ide/spyder/releases/latest/' + 'download/Spyder_64bit_lite.exe') + is_full_installer = (is_module_installed('numpy') or + is_module_installed('pandas')) + if is_full_installer: url = ( 'https://github.com/spyder-ide/spyder/releases/latest/' - 'download/Spyder_64bit_lite.exe') - is_full_installer = (is_module_installed('numpy') or - is_module_installed('pandas')) - if is_full_installer: - url = ( - 'https://github.com/spyder-ide/spyder/releases/latest/' - 'download/Spyder_64bit_full.exe') - logger.debug(f"Downloading installer from: {url}") - download = urlretrieve(url, - destination, - reporthook=self._progress_reporter) - self._change_update_installation_status(status=INSTALLING) - install = subprocess.Popen(destination, shell=True) - install.communicate() + 'download/Spyder_64bit_full.exe') + logger.debug(f"Downloading installer from: {url}") + download = urlretrieve(url, + destination, + reporthook=self._progress_reporter) + self._change_update_installation_status(status=INSTALLING) + subprocess.run(['start', destination], shell=True) + except UpdateInstallationCancelledException: self._change_update_installation_status(status=CANCELLED) finally: From c79e778be6b267e6c347013833638cc9135266d1 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Tue, 23 Aug 2022 12:49:08 -0500 Subject: [PATCH 27/49] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/container.py | 3 ++ spyder/plugins/application/widgets/install.py | 6 ++-- spyder/plugins/application/widgets/status.py | 35 ++++++++++++++----- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 6cff3127d82..db731bf8f2f 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -107,6 +107,9 @@ def __init__(self, name, plugin, parent=None): def setup(self): self.application_update_status = ApplicationUpdateStatus(parent=self) + self.application_update_status.sig_check_for_updates_requested.connect( + self.check_updates + ) self.application_update_status.set_no_status() # Compute dependencies in a thread to not block the interface. diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 8d92081b877..5c5da64085f 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -33,10 +33,10 @@ # Update installation process statuses NO_STATUS = __version__ -DOWNLOADING_INSTALLER = _("Downloading installer") -INSTALLING = _("Installing") +DOWNLOADING_INSTALLER = _("Downloading update") +INSTALLING = _("Installing update") FINISHED = _("Installation finished") -PENDING = _("Pending update") +PENDING = _("Update available") CHECKING = _("Checking for updates") CANCELLED = _("Cancelled") diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 729585a51ac..18df10a8120 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -12,16 +12,17 @@ import logging # Third party imports -from qtpy.QtCore import Slot +from qtpy.QtCore import QPoint, Signal, Slot +from qtpy.QtWidgets import QMenu # Local imports -from spyder import get_versions from spyder.api.widgets.status import StatusBarWidget from spyder.config.base import _, is_pynsist from spyder.plugins.application.widgets.install import ( UpdateInstallerDialog, NO_STATUS, DOWNLOADING_INSTALLER, INSTALLING, FINISHED, PENDING, CHECKING, CANCELLED) from spyder.utils.icon_manager import ima +from spyder.utils.qthelpers import add_actions, create_action logger = logging.getLogger(__name__) @@ -31,6 +32,8 @@ class ApplicationUpdateStatus(StatusBarWidget): BASE_TOOLTIP = _("Application update status") ID = 'application_update_status' + sig_check_for_updates_requested = Signal() + def __init__(self, parent): self.tooltip = self.BASE_TOOLTIP @@ -38,8 +41,10 @@ def __init__(self, parent): # Installation dialog self.installer = UpdateInstallerDialog(self) + # Check for updates action menu + self.menu = QMenu(self) - self.sig_clicked.connect(self.show_installation_dialog) + self.sig_clicked.connect(self.show_installation_dialog_or_menu) self.installer.sig_installation_status.connect( self.set_value) @@ -91,10 +96,24 @@ def set_no_status(self): self.spinner.stop() @Slot() - def show_installation_dialog(self): - """Show installation dialog.""" - if ((not self.tooltip == self.BASE_TOOLTIP and not - self.tooltip == PENDING) and is_pynsist()): + def show_installation_dialog_or_menu(self): + """Show installation dialog or menu.""" + value = self.value.split(":")[-1].strip() + if (not self.tooltip == self.BASE_TOOLTIP and not + value == PENDING and is_pynsist()): self.installer.show() - elif ((self.tooltip == PENDING) and is_pynsist()): + elif value == PENDING and is_pynsist(): self.installer.continue_install() + elif value == NO_STATUS: + self.menu.clear() + check_for_updates_action = create_action( + self, + text=_("Check for updates..."), + triggered=self.sig_check_for_updates_requested.emit + ) + add_actions(self.menu, [check_for_updates_action]) + rect = self.contentsRect() + os_height = 7 if os.name == 'nt' else 12 + pos = self.mapToGlobal( + rect.topLeft() + QPoint(-10, -rect.height() - os_height)) + self.menu.popup(pos) From 13b5b374797728b70abb33133846a62ce197de80 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Tue, 23 Aug 2022 13:38:22 -0500 Subject: [PATCH 28/49] Commit suggestion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/widgets/status.py | 1 + 1 file changed, 1 insertion(+) diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 18df10a8120..ec3cf90f12d 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -10,6 +10,7 @@ # Standard library imports import logging +import os # Third party imports from qtpy.QtCore import QPoint, Signal, Slot From 78b5f44421d92f72892b7e7bb58cec985598ffd6 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 23 Aug 2022 15:44:26 -0500 Subject: [PATCH 29/49] commit to try to handle the macOS case --- spyder/plugins/application/container.py | 5 +++-- spyder/plugins/application/widgets/install.py | 21 +++++++++++-------- spyder/plugins/application/widgets/status.py | 10 +++++---- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index db731bf8f2f..ff2b4168127 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -27,7 +27,8 @@ from spyder.api.widgets.main_container import PluginMainContainer from spyder.utils.installers import InstallerMissingDependencies from spyder.config.utils import is_anaconda -from spyder.config.base import get_conf_path, get_debug_level, is_pynsist +from spyder.config.base import (get_conf_path, get_debug_level, is_pynsist, + running_in_mac_app) from spyder.plugins.application.widgets.status import ApplicationUpdateStatus from spyder.plugins.console.api import ConsoleActions from spyder.utils.qthelpers import start_file, DialogManager @@ -314,7 +315,7 @@ def _check_updates_ready(self): "conda install spyder={}

" ).format(latest_release) else: - if is_pynsist(): + if is_pynsist() or running_in_mac_app(): box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) content = _( diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 5c5da64085f..a59d162da53 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -239,22 +239,25 @@ def _download_install(self): try: logger.debug("Downloading installer executable") tmpdir = tempfile.mkdtemp(prefix="Spyder-") - destination = os.path.join(tmpdir, 'updateSpyder.exe') - url = ( - 'https://github.com/spyder-ide/spyder/releases/latest/' - 'download/Spyder_64bit_lite.exe') is_full_installer = (is_module_installed('numpy') or is_module_installed('pandas')) - if is_full_installer: - url = ( - 'https://github.com/spyder-ide/spyder/releases/latest/' - 'download/Spyder_64bit_full.exe') + if os.name == 'nt': + name = 'Spyder_64bit_{}.exe'.format('full' if is_full_installer + else 'lite') + else: + name = 'Spyder{}.dmg'.format('' if is_full_installer + else '-Lite') + + url = ('https://github.com/spyder-ide/spyder/releases/latest/' + f'download/{name}') + destination = os.path.join(tmpdir, name) logger.debug(f"Downloading installer from: {url}") download = urlretrieve(url, destination, reporthook=self._progress_reporter) self._change_update_installation_status(status=INSTALLING) - subprocess.run(['start', destination], shell=True) + cmd = ('start' if os.name == 'nt' else 'open') + subprocess.run([cmd, destination], shell=True) except UpdateInstallationCancelledException: self._change_update_installation_status(status=CANCELLED) diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index ec3cf90f12d..69f4eed9121 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -18,7 +18,7 @@ # Local imports from spyder.api.widgets.status import StatusBarWidget -from spyder.config.base import _, is_pynsist +from spyder.config.base import _, is_pynsist, running_in_mac_app from spyder.plugins.application.widgets.install import ( UpdateInstallerDialog, NO_STATUS, DOWNLOADING_INSTALLER, INSTALLING, FINISHED, PENDING, CHECKING, CANCELLED) @@ -100,10 +100,12 @@ def set_no_status(self): def show_installation_dialog_or_menu(self): """Show installation dialog or menu.""" value = self.value.split(":")[-1].strip() - if (not self.tooltip == self.BASE_TOOLTIP and not - value == PENDING and is_pynsist()): + if ((not self.tooltip == self.BASE_TOOLTIP + and not value == PENDING) + and (is_pynsist() or running_in_mac_app())): self.installer.show() - elif value == PENDING and is_pynsist(): + elif (value == PENDING and + (is_pynsist() or running_in_mac_app())): self.installer.continue_install() elif value == NO_STATUS: self.menu.clear() From ca65c593dd7f1d7acaadcfe4bd22af0514582065 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Wed, 24 Aug 2022 11:47:51 -0500 Subject: [PATCH 30/49] commit to try to handle the macOS case Co-authored-by: Ryan Clary <9618975+mrclary@users.noreply.github.com> --- spyder/plugins/application/widgets/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index a59d162da53..0dae3ed3f0a 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -257,7 +257,7 @@ def _download_install(self): reporthook=self._progress_reporter) self._change_update_installation_status(status=INSTALLING) cmd = ('start' if os.name == 'nt' else 'open') - subprocess.run([cmd, destination], shell=True) + subprocess.run(' '.join([cmd, destination]), shell=True) except UpdateInstallationCancelledException: self._change_update_installation_status(status=CANCELLED) From cad47941c300b14ad494bb1da3c825194943dcf0 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Fri, 26 Aug 2022 08:23:57 -0500 Subject: [PATCH 31/49] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/container.py | 10 ---------- spyder/plugins/application/widgets/install.py | 6 +++--- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index ff2b4168127..207627960b2 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -91,11 +91,6 @@ class ApplicationContainer(PluginMainContainer): Signal to load a log file """ - sig_show_status_bar_widget = Signal(bool) - """ - Signal to show the widget in status bar - """ - def __init__(self, name, plugin, parent=None): super().__init__(name, plugin, parent) @@ -322,11 +317,6 @@ def _check_updates_ready(self): "Would you like to automatically download and" " install the latest version of Spyder?

" ) - else: - content = _( - "Click this link to " - "download it.

" - ).format(url_r) msg = header + content + footer box.setText(msg) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 0dae3ed3f0a..158b831ef7a 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -105,7 +105,7 @@ def update_installation_status(self, status): self.install_info.setText(INSTALL_INFO_MESSAGES[status]) if status == INSTALLING: self._progress_bar.setRange(0, 0) - self.cancel_button.hide() + self.cancel_button.setEnabled(False) def update_installation_progress(self, current_value, total): """Update installation progress bar.""" @@ -153,10 +153,10 @@ def __init__(self, parent): self._installation_widget.cancel_button.clicked.connect( self.cancel_install) - # Show integration widget + # Show installation widget self.setup() - def setup(self, installation=False): + def setup(self): """Setup visibility of widgets.""" self._installation_widget.setVisible(True) self.adjustSize() From ab1bdd796094ca374118c5f4e0fa95c817d3dcf2 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Fri, 26 Aug 2022 08:29:10 -0500 Subject: [PATCH 32/49] Apply suggestions from code review --- spyder/plugins/application/container.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 207627960b2..44f373e862a 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -309,14 +309,13 @@ def _check_updates_ready(self): "conda update anaconda
" "conda install spyder={}

" ).format(latest_release) - else: - if is_pynsist() or running_in_mac_app(): - box.setStandardButtons(QMessageBox.Yes | - QMessageBox.No) - content = _( - "Would you like to automatically download and" - " install the latest version of Spyder?

" - ) + elif is_pynsist() or running_in_mac_app(): + box.setStandardButtons(QMessageBox.Yes | + QMessageBox.No) + content = _( + "Would you like to automatically download and" + " install the latest version of Spyder?

" + ) msg = header + content + footer box.setText(msg) From 264a8547ca432cf21bf72d070bdd345adc97d024 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Mon, 29 Aug 2022 13:17:35 -0500 Subject: [PATCH 33/49] check updates --- spyder/plugins/application/container.py | 3 ++- spyder/plugins/application/widgets/install.py | 21 ++++++++++++------- spyder/plugins/application/widgets/status.py | 6 ++++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 44f373e862a..f82251863e8 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -323,7 +323,8 @@ def _check_updates_ready(self): box.exec_() if box.result() == QMessageBox.Yes: - self.application_update_status.start_installation() + self.application_update_status.start_installation( + latest_release=latest_release) elif(box.result() == QMessageBox.No): self.application_update_status.set_status_pending() check_updates = box.is_checked() diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 158b831ef7a..4c45bb700d3 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -134,7 +134,7 @@ def __init__(self, parent): self.setWindowFlags(Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) self._parent = parent self._installation_widget = UpdateInstallation(self) - + self.latest_release_version = "" # Layout installer_layout = QVBoxLayout() installer_layout.addWidget(self._installation_widget) @@ -238,7 +238,7 @@ def cancel_thread_install_update(self): def _download_install(self): try: logger.debug("Downloading installer executable") - tmpdir = tempfile.mkdtemp(prefix="Spyder-") + tmpdir = tempfile.gettempdir() is_full_installer = (is_module_installed('numpy') or is_module_installed('pandas')) if os.name == 'nt': @@ -250,11 +250,15 @@ def _download_install(self): url = ('https://github.com/spyder-ide/spyder/releases/latest/' f'download/{name}') - destination = os.path.join(tmpdir, name) - logger.debug(f"Downloading installer from: {url}") - download = urlretrieve(url, - destination, - reporthook=self._progress_reporter) + path_destination = os.path.join(tmpdir, 'spyder', + self.latest_release_version) + os.makedirs(path_destination, exist_ok=True) + destination = os.path.join(path_destination, name) + if (not os.path.isfile(destination)): + logger.debug(f"Downloading installer from: {url}") + download = urlretrieve(url, + destination, + reporthook=self._progress_reporter) self._change_update_installation_status(status=INSTALLING) cmd = ('start' if os.name == 'nt' else 'open') subprocess.run(' '.join([cmd, destination]), shell=True) @@ -264,8 +268,9 @@ def _download_install(self): finally: self._change_update_installation_status(status=PENDING) - def start_installation_update(self): + def start_installation_update(self, latest_release_version): """Start the installation update thread and set downloading status.""" + self.latest_release_version = latest_release_version self.cancelled = False self._change_update_installation_status( status=DOWNLOADING_INSTALLER) diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 69f4eed9121..6f75ff50c33 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -59,6 +59,8 @@ def set_value(self, value): "dialog again") self.spinner.show() self.spinner.start() + self.installer.show() + elif value == PENDING: self.tooltip = value self.spinner.hide() @@ -78,8 +80,8 @@ def get_tooltip(self): def get_icon(self): return ima.icon('spyder_about') - def start_installation(self): - self.installer.start_installation_update() + def start_installation(self, latest_release): + self.installer.start_installation_update(latest_release) def set_status_pending(self): self.set_value(PENDING) From 0053341da31cb95d7db81026d7bf77ca58b9ef8b Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Tue, 30 Aug 2022 12:38:33 -0500 Subject: [PATCH 34/49] Update spyder/plugins/application/widgets/install.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/widgets/install.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 4c45bb700d3..0914f933af1 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -250,14 +250,14 @@ def _download_install(self): url = ('https://github.com/spyder-ide/spyder/releases/latest/' f'download/{name}') - path_destination = os.path.join(tmpdir, 'spyder', - self.latest_release_version) - os.makedirs(path_destination, exist_ok=True) - destination = os.path.join(path_destination, name) - if (not os.path.isfile(destination)): - logger.debug(f"Downloading installer from: {url}") + installer_dir_path = os.path.join(tmpdir, 'spyder', 'updates', + self.latest_release_version) + os.makedirs(installer_dir_path, exist_ok=True) + installer_path = os.path.join(installer_dir_path, name) + if (not os.path.isfile(installer_path)): + logger.debug(f"Downloading installer from {url} to {installer_path}") download = urlretrieve(url, - destination, + installer_path, reporthook=self._progress_reporter) self._change_update_installation_status(status=INSTALLING) cmd = ('start' if os.name == 'nt' else 'open') From 3220a31e8b03c644eff03bb17bbb731b9d881942 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Wed, 31 Aug 2022 12:24:15 -0500 Subject: [PATCH 35/49] Apply suggestions from code review Co-authored-by: C.A.M. Gerlach --- spyder/plugins/application/container.py | 4 ++-- spyder/plugins/application/widgets/install.py | 6 +++--- spyder/plugins/application/widgets/status.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index f82251863e8..33d7e87c031 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -313,8 +313,8 @@ def _check_updates_ready(self): box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) content = _( - "Would you like to automatically download and" - " install the latest version of Spyder?

" + "Would you like to automatically download and " + "install it?

" ) msg = header + content + footer diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 0914f933af1..510f5b21c96 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -78,7 +78,7 @@ def __init__(self, parent): self._progress_label = QLabel(_('Downloading')) self.install_info = QLabel( - _("Downloading the latest Spyder version
")) + _("Downloading Spyder update
")) button_layout = QHBoxLayout() self.ok_button = QPushButton(_('OK')) @@ -180,8 +180,8 @@ def continue_install(self): Continue the installation in progress by downloading the installer. """ reply = QMessageBox(icon=QMessageBox.Question, - text=_("Would you like to automatically download a" - "nd install the latest version of Spyder?" + text=_("Would you like to automatically download " + "and install the latest version of Spyder?" "

"), parent=self._parent) reply.setWindowTitle("Spyder") diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index 6f75ff50c33..bce94558ab4 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -29,7 +29,7 @@ class ApplicationUpdateStatus(StatusBarWidget): - """Status bar widget for Application update status.""" + """Status bar widget for application update status.""" BASE_TOOLTIP = _("Application update status") ID = 'application_update_status' @@ -56,7 +56,7 @@ def set_value(self, value): self.tooltip = _("Update installation will continue in the " "background.\n" "Click here to show the installation " - "dialog again") + "dialog again.") self.spinner.show() self.spinner.start() self.installer.show() From 595c8ec6e4fecdd6d374fe870395036eb803c922 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Wed, 31 Aug 2022 23:47:37 -0500 Subject: [PATCH 36/49] Update spyder/plugins/application/widgets/install.py Co-authored-by: Ryan Clary <9618975+mrclary@users.noreply.github.com> --- spyder/plugins/application/widgets/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 510f5b21c96..66dd90aac57 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -261,7 +261,7 @@ def _download_install(self): reporthook=self._progress_reporter) self._change_update_installation_status(status=INSTALLING) cmd = ('start' if os.name == 'nt' else 'open') - subprocess.run(' '.join([cmd, destination]), shell=True) + subprocess.run(' '.join([cmd, installer_path]), shell=True) except UpdateInstallationCancelledException: self._change_update_installation_status(status=CANCELLED) From 218c20faf69c5f7816ff1170723c5cd3428827a1 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Thu, 1 Sep 2022 00:00:53 -0500 Subject: [PATCH 37/49] check updates --- spyder/plugins/application/container.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 33d7e87c031..9dd2a6bf56b 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -21,7 +21,7 @@ from qtpy.QtWidgets import QAction, QMessageBox, QPushButton # Local imports -from spyder import __docs_url__, __forum_url__, __trouble_url__ +from spyder import __docs_url__, __forum_url__, __trouble_url__, __version__ from spyder import dependencies from spyder.api.translations import get_translation from spyder.api.widgets.main_container import PluginMainContainer @@ -292,8 +292,9 @@ def _check_updates_ready(self): if update_available: self.application_update_status.set_status_pending() - header = _("Spyder {} is available!

").format( - latest_release) + header = _("Spyder {} is available! ", + "(you have {})

").format( + latest_release,) footer = _( "For more information visit our " "installation guide." From 5e1870cd73fa97bb8438209d18f503a02260182a Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Thu, 1 Sep 2022 14:21:19 -0500 Subject: [PATCH 38/49] Update spyder/plugins/application/container.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index 9dd2a6bf56b..c42e71af676 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -294,7 +294,7 @@ def _check_updates_ready(self): header = _("Spyder {} is available! ", "(you have {})

").format( - latest_release,) + latest_release, __version__) footer = _( "For more information visit our " "installation guide." From c63b2a8b203d5c6b460dbea971634fe4ad15cfd7 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Thu, 1 Sep 2022 14:44:05 -0500 Subject: [PATCH 39/49] Update spyder/plugins/application/widgets/install.py Co-authored-by: C.A.M. Gerlach --- spyder/plugins/application/widgets/install.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 66dd90aac57..9886ec270f5 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -38,16 +38,15 @@ FINISHED = _("Installation finished") PENDING = _("Update available") CHECKING = _("Checking for updates") -CANCELLED = _("Cancelled") +CANCELLED = _("Cancelled update") INSTALL_INFO_MESSAGES = { - DOWNLOADING_INSTALLER: _("Downloading Spyder latest " - "release installer executable"), - INSTALLING: _("Installing Spyder latest release"), + DOWNLOADING_INSTALLER: _("Downloading latest Spyder update"), + INSTALLING: _("Installing Spyder update"), FINISHED: _("Spyder update installation finished"), - PENDING: _("An update is pending"), - CHECKING: _("Checking for updates"), - CANCELLED: _("Update cancelled") + PENDING: _("Spyder update available to download"), + CHECKING: _("Checking for Spyder updates"), + CANCELLED: _("Spyder update cancelled") } From 6a8bb76f95802e0da45eb61dce5d1aa02a390758 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Fri, 2 Sep 2022 09:44:37 -0500 Subject: [PATCH 40/49] check updates --- spyder/plugins/application/widgets/install.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 9886ec270f5..44a1d8bdfea 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -254,7 +254,8 @@ def _download_install(self): os.makedirs(installer_dir_path, exist_ok=True) installer_path = os.path.join(installer_dir_path, name) if (not os.path.isfile(installer_path)): - logger.debug(f"Downloading installer from {url} to {installer_path}") + logger.debug( + f"Downloading installer from {url} to {installer_path}") download = urlretrieve(url, installer_path, reporthook=self._progress_reporter) From 84addc38e4e1402ed6b4e968b493db0aa6a2d0a6 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Mon, 5 Sep 2022 11:28:06 -0500 Subject: [PATCH 41/49] delete old updates --- spyder/plugins/application/widgets/install.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 44a1d8bdfea..056dd392efd 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -249,9 +249,16 @@ def _download_install(self): url = ('https://github.com/spyder-ide/spyder/releases/latest/' f'download/{name}') - installer_dir_path = os.path.join(tmpdir, 'spyder', 'updates', + dir_path = os.path.join(tmpdir, 'spyder', 'updates') + os.makedirs(dir_path, exist_ok=True) + installer_dir_path = os.path.join(dir_path, self.latest_release_version) os.makedirs(installer_dir_path, exist_ok=True) + for file in os.listdir(dir_path): + if file not in [__version__, self.latest_release_version]: + remove = os.path.join(dir_path, file) + os.remove(remove) + installer_path = os.path.join(installer_dir_path, name) if (not os.path.isfile(installer_path)): logger.debug( From 29c3b989f451e58e7f0c8bae9d4291c8c256c17f Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Tue, 6 Sep 2022 13:56:30 -0500 Subject: [PATCH 42/49] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index c42e71af676..725bfa7ef31 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -292,7 +292,7 @@ def _check_updates_ready(self): if update_available: self.application_update_status.set_status_pending() - header = _("Spyder {} is available! ", + header = _("Spyder {} is available! " "(you have {})

").format( latest_release, __version__) footer = _( From ead5d96ac65a7c7502c266866ef2235160a0ab42 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Wed, 7 Sep 2022 22:32:04 -0500 Subject: [PATCH 43/49] Apply suggestions --- spyder/plugins/application/container.py | 3 ++- spyder/plugins/application/widgets/install.py | 5 ++++- spyder/plugins/application/widgets/status.py | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index c42e71af676..2969dd4674c 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -327,7 +327,8 @@ def _check_updates_ready(self): self.application_update_status.start_installation( latest_release=latest_release) elif(box.result() == QMessageBox.No): - self.application_update_status.set_status_pending() + self.application_update_status.set_status_pending( + latest_release=latest_release) check_updates = box.is_checked() elif feedback: msg = _("Spyder is up to date.") diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 056dd392efd..1bf48e61de8 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -188,7 +188,7 @@ def continue_install(self): reply.setStandardButtons(QMessageBox.Yes | QMessageBox.No) reply.exec_() if reply.result() == QMessageBox.Yes: - self.start_installation_update() + self.start_installation_update(self.latest_release_version) else: self._change_update_installation_status(status=PENDING) @@ -275,6 +275,9 @@ def _download_install(self): finally: self._change_update_installation_status(status=PENDING) + def save_latest_release(self, latest_release_version): + self.latest_release_version = latest_release_version + def start_installation_update(self, latest_release_version): """Start the installation update thread and set downloading status.""" self.latest_release_version = latest_release_version diff --git a/spyder/plugins/application/widgets/status.py b/spyder/plugins/application/widgets/status.py index bce94558ab4..e00c74479b8 100644 --- a/spyder/plugins/application/widgets/status.py +++ b/spyder/plugins/application/widgets/status.py @@ -83,8 +83,9 @@ def get_icon(self): def start_installation(self, latest_release): self.installer.start_installation_update(latest_release) - def set_status_pending(self): + def set_status_pending(self, latest_release): self.set_value(PENDING) + self.installer.save_latest_release(latest_release) self.spinner.hide() self.spinner.stop() From daac4b1166daf94160ad5a1e160c470b9bb3ae10 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Fri, 9 Sep 2022 07:56:11 -0500 Subject: [PATCH 44/49] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré Co-authored-by: C.A.M. Gerlach --- spyder/plugins/application/container.py | 5 +++-- spyder/plugins/application/widgets/install.py | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/spyder/plugins/application/container.py b/spyder/plugins/application/container.py index a29880f3e9a..6f48ffada9e 100644 --- a/spyder/plugins/application/container.py +++ b/spyder/plugins/application/container.py @@ -290,13 +290,14 @@ def _check_updates_ready(self): else: if update_available: - self.application_update_status.set_status_pending() + self.application_update_status.set_status_pending( + latest_release=latest_release) header = _("Spyder {} is available! " "(you have {})

").format( latest_release, __version__) footer = _( - "For more information visit our " + "For more information, visit our " "installation guide." ).format(url_i) if is_anaconda(): diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 1bf48e61de8..09f4d4ee93f 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -41,11 +41,11 @@ CANCELLED = _("Cancelled update") INSTALL_INFO_MESSAGES = { - DOWNLOADING_INSTALLER: _("Downloading latest Spyder update"), - INSTALLING: _("Installing Spyder update"), - FINISHED: _("Spyder update installation finished"), - PENDING: _("Spyder update available to download"), - CHECKING: _("Checking for Spyder updates"), + DOWNLOADING_INSTALLER: _("Downloading Spyder {version}"), + INSTALLING: _("Installing Spyder {version}"), + FINISHED: _("Finished installing Spyder {version}"), + PENDING: _("Spyder {version} available to download"), + CHECKING: _("Checking for new Spyder version"), CANCELLED: _("Spyder update cancelled") } From e12713faca91b45f352cfdf793f218c15322cdb9 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Fri, 9 Sep 2022 08:06:55 -0500 Subject: [PATCH 45/49] Apply suggestions --- spyder/plugins/application/widgets/install.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 09f4d4ee93f..dcec761cbf6 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -98,10 +98,11 @@ def __init__(self, parent): self.setLayout(general_layout) - def update_installation_status(self, status): + def update_installation_status(self, status, latestVersion): """Update installation status (downloading, installing, finished).""" self._progress_label.setText(status) - self.install_info.setText(INSTALL_INFO_MESSAGES[status]) + self.install_info.setText(INSTALL_INFO_MESSAGES[status].format( + version=latestVersion)) if status == INSTALLING: self._progress_bar.setRange(0, 0) self.cancel_button.setEnabled(False) @@ -144,7 +145,7 @@ def __init__(self, parent): self._installation_widget.update_installation_progress) self.sig_installation_status.connect( self._installation_widget.update_installation_status) - self.sig_installation_status.connect( + self.sig_installation_status_finish.connect( self.finished_installation) self._installation_widget.ok_button.clicked.connect( @@ -220,6 +221,8 @@ def _change_update_installation_status(self, status=NO_STATUS): logger.debug(f"Installation status: {status}") self.status = status self.sig_installation_status.emit(self.status) + self.sig_installation_status.emit(self.status, + self.latest_release_version) def _progress_reporter(self, block_number, read_size, total_size): progress = 0 From 2095abef78e3b8f6f603afaf7cb552cf58eed486 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Fri, 9 Sep 2022 10:05:26 -0500 Subject: [PATCH 46/49] Apply suggestions --- spyder/plugins/application/widgets/install.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index dcec761cbf6..721ef38b8a9 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -123,7 +123,9 @@ class UpdateInstallerDialog(QDialog): # Signal to get the current status of the update installation # str: Status string - sig_installation_status = Signal(str) + sig_installation_status = Signal(str, str) + + def __init__(self, parent): @@ -145,8 +147,6 @@ def __init__(self, parent): self._installation_widget.update_installation_progress) self.sig_installation_status.connect( self._installation_widget.update_installation_status) - self.sig_installation_status_finish.connect( - self.finished_installation) self._installation_widget.ok_button.clicked.connect( self.close_installer) @@ -220,7 +220,7 @@ def _change_update_installation_status(self, status=NO_STATUS): """Set the installation status.""" logger.debug(f"Installation status: {status}") self.status = status - self.sig_installation_status.emit(self.status) + self.finished_installation(self.status) self.sig_installation_status.emit(self.status, self.latest_release_version) From 62c83a953638082dc9e2589748b3a7d770fc5127 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Fri, 9 Sep 2022 10:07:41 -0500 Subject: [PATCH 47/49] pycodestyle --- spyder/plugins/application/widgets/install.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 721ef38b8a9..75e79fb6ab0 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -125,8 +125,6 @@ class UpdateInstallerDialog(QDialog): # str: Status string sig_installation_status = Signal(str, str) - - def __init__(self, parent): self.cancelled = False From 1d38d96af77b95da83080d3e95680babde85615a Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Tue, 13 Sep 2022 17:24:35 -0500 Subject: [PATCH 48/49] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré --- spyder/plugins/application/widgets/install.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spyder/plugins/application/widgets/install.py b/spyder/plugins/application/widgets/install.py index 75e79fb6ab0..edf2dfe02c4 100644 --- a/spyder/plugins/application/widgets/install.py +++ b/spyder/plugins/application/widgets/install.py @@ -98,11 +98,11 @@ def __init__(self, parent): self.setLayout(general_layout) - def update_installation_status(self, status, latestVersion): + def update_installation_status(self, status, latest_version): """Update installation status (downloading, installing, finished).""" self._progress_label.setText(status) self.install_info.setText(INSTALL_INFO_MESSAGES[status].format( - version=latestVersion)) + version=latest_version)) if status == INSTALLING: self._progress_bar.setRange(0, 0) self.cancel_button.setEnabled(False) From 8ba83a7ed06074fbc336d5dff4800010c65aac82 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 13 Sep 2022 18:11:35 -0500 Subject: [PATCH 49/49] status bar changes --- spyder/plugins/statusbar/plugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spyder/plugins/statusbar/plugin.py b/spyder/plugins/statusbar/plugin.py index b35d350727b..956c380fe70 100644 --- a/spyder/plugins/statusbar/plugin.py +++ b/spyder/plugins/statusbar/plugin.py @@ -49,7 +49,8 @@ class StatusBar(SpyderPluginV2): INTERNAL_WIDGETS_IDS = { 'clock_status', 'cpu_status', 'memory_status', 'read_write_status', 'eol_status', 'encoding_status', 'cursor_position_status', - 'vcs_status', 'lsp_status', 'kite_status', 'completion_status'} + 'vcs_status', 'lsp_status', 'kite_status', 'completion_status', + 'interpreter_status', 'application_update_status'} # ---- SpyderPluginV2 API @staticmethod @@ -216,7 +217,8 @@ def _organize_status_widgets(self): internal_layout = [ 'clock_status', 'cpu_status', 'memory_status', 'read_write_status', 'eol_status', 'encoding_status', 'cursor_position_status', - 'vcs_status', 'lsp_status', 'kite_status', 'completion_status'] + 'vcs_status', 'lsp_status', 'kite_status', 'completion_status', + 'interpreter_status', 'application_update_status'] external_left = list(self.EXTERNAL_LEFT_WIDGETS.keys()) # Remove all widgets from the statusbar, except the external right