-- Added Test Tags field (new, since Robot Framework 6.0) to Test Suites settings. This field will replace Default and Force Tags settings, after Robot Framework 7.0
+- Added auto update check when development version is installed
+
- Added menu option ``Help->Check for Upgrade`` which allows to force update check and install development version
+
- Added ``Upgrade Now`` action to update dialog.
+
- Added Test Tags field (new, since Robot Framework 6.0) to Test Suites settings. This field will replace Default and Force Tags settings, after Robot Framework 7.0
-
Added content help pop-up on Text Editor by pressing ``Ctrl`` for text at cursor position or selected autocomplete list item
-
diff --git a/src/robotide/application/releasenotes.py b/src/robotide/application/releasenotes.py
index ece29db8d..b904ca498 100644
--- a/src/robotide/application/releasenotes.py
+++ b/src/robotide/application/releasenotes.py
@@ -168,6 +168,9 @@ def set_content(self, html_win, content):
New Features and Fixes Highlights
+- Added auto update check when development version is installed
+- Added menu option Help->Check for Upgrade which allows to force update check and install development version
+- Added Upgrade Now action to update dialog.
- Added Test Tags field (new, since Robot Framework 6.0) to Test Suites settings. This field will replace Default and Force Tags settings, after Robot Framework 7.0
- Improved RIDE Log and Parser Log windows to allow Zoom In/Out with Ctrl-Mouse Wheel
- Hide continuation markers in Project Tree
@@ -243,6 +246,6 @@ def set_content(self, html_win, content):
python -m robotide.postinstall -install
-
RIDE {VERSION} was released on 7/Oct/2023.
+
RIDE {VERSION} was released on 10/Oct/2023.
"""
diff --git a/src/robotide/application/updatenotifier.py b/src/robotide/application/updatenotifier.py
index e5474cdb4..14f6312df 100644
--- a/src/robotide/application/updatenotifier.py
+++ b/src/robotide/application/updatenotifier.py
@@ -12,7 +12,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
+import subprocess
+import sys
# Configure wx uversion to allow running test app in __main__
@@ -20,12 +21,14 @@
import urllib.request as urllib2
import xmlrpc.client as xmlrpclib
+import psutil
import wx
from wx import Colour
from .. import version
-from ..utils.versioncomparator import cmp_versions
+from ..utils.versioncomparator import cmp_versions, parse_version
from ..widgets import ButtonWithHandler, HtmlWindow, RIDEDialog
+from ..postinstall.__main__ import MessageDialog
_CHECK_FOR_UPDATES_SETTING = "check for updates"
_LAST_UPDATE_CHECK_SETTING = "last update check"
@@ -39,9 +42,16 @@ class UpdateNotifierController(object):
def __init__(self, settings):
self._settings = settings
- def notify_update_if_needed(self, update_notification_callback):
- if self._should_check() and self._is_new_version_available():
+ def notify_update_if_needed(self, update_notification_callback, ignore_check_condition=False):
+ if ignore_check_condition:
+ dev_version = checking_version = True
+ else:
+ checking_version = self._should_check()
+ dev_version = parse_version(self.VERSION).is_devrelease
+ if checking_version and self._is_new_version_available():
update_notification_callback(self._newest_version, self._download_url, self._settings)
+ if checking_version and dev_version:
+ upgrade_from_dev_dialog(version_installed=self.VERSION)
def _should_check(self):
if self._settings.get(_CHECK_FOR_UPDATES_SETTING, None) is None:
@@ -82,6 +92,91 @@ def _get_response(params, method):
return xml
+def upgrade_from_dev_dialog(version_installed):
+ VERSION = None
+ dev_version = urllib2.urlopen('https://raw.githubusercontent.com/robotframework/'
+ 'RIDE/master/src/robotide/version.py', timeout=1).read().decode('utf-8')
+ master_code = compile(dev_version, 'version', 'exec')
+ main_dict = {'VERSION': VERSION}
+ exec(master_code, main_dict) # defines VERSION
+ if cmp_versions(version_installed, main_dict['VERSION']) == -1:
+ # Here is the Menu Help->Upgrade insertion part, try to highlight menu
+ if not _askyesno("Upgrade?", f"New development version is available.\nYou may install"
+ f" version {main_dict['VERSION']} with:\npip install -U https://github.com/"
+ f"robotframework/RIDE/archive/master.zip", wx.GetActiveWindow()):
+ return False
+ else:
+ command = sys.executable + " -m pip install -U https://github.com/robotframework/RIDE/archive/master.zip"
+ do_upgrade(command)
+ return True
+ else:
+ _askyesno("No Upgrade Available", "You have the latest version of RIDE.\nHave a nice day :)",
+ wx.GetActiveWindow())
+ return False
+
+
+def _askyesno(title, message, frame=None):
+ if frame is None:
+ _ = wx.App()
+ parent = wx.Frame(None, size=(0, 0))
+ else:
+ parent = wx.Frame(frame, size=(0, 0))
+ parent.CenterOnScreen()
+ dlg = MessageDialog(parent, message, title, ttl=8)
+ dlg.Fit()
+ result = dlg.ShowModal() in [wx.ID_YES, wx.ID_OK]
+ # print("DEBUG: updatenotifier _askyesno Result %s" % result)
+ if dlg:
+ dlg.Destroy()
+ # parent.Destroy()
+ return result
+
+
+def _add_content_to_clipboard(content):
+ wx.TheClipboard.Open()
+ wx.TheClipboard.SetData(wx.TextDataObject(content))
+ wx.TheClipboard.Close()
+
+
+def do_upgrade(command):
+ _add_content_to_clipboard(command)
+ # print("DEBUG: Here will be the installation step.")
+ my_pid = psutil.Process()
+ my_pip = subprocess.Popen(command.split(' '), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ result = None
+ count = 0
+ while not result and count < 60:
+ count += 1
+ outs, errs = my_pip.communicate()
+ # DEBUG: Add output to a notebook tab
+ print(f"{outs}\n")
+ result = my_pip.returncode
+ if result == 0:
+ break
+ """ DEBUG: need to get outs line by line
+ except subprocess.SubprocessError:
+ my_pip.kill()
+ outs, errs = my_pip.communicate()
+ result = False
+ # DEBUG: Add output to a notebook tab
+ print(f"{outs}\n")
+ print(f"{errs}\n")
+ """
+ time.sleep(1)
+ if result != 0:
+ _askyesno("Failed to Upgrade", "An error occurred when installing new version",
+ wx.GetActiveWindow())
+ return False
+ command = sys.executable + " -m robotide.__init__ --noupdatecheck"
+ wx.CallLater(500, subprocess.Popen, command.split(' '), start_new_session=True)
+ # Wait 10 seconds before trying to kill this process
+ """ Not working well:
+ wx.CallLater(10000, psutil.Process.kill, my_pid.pid)
+ """
+ _askyesno("Completed Upgrade", f"You should close this RIDE (Process ID = {my_pid.pid})",
+ wx.GetActiveWindow())
+
+
class LocalHtmlWindow(HtmlWindow):
def __init__(self, parent, size=(600, 400)):
HtmlWindow.__init__(self, parent, size)
@@ -96,6 +191,7 @@ class UpdateDialog(RIDEDialog):
def __init__(self, uversion, url, settings):
self._settings = settings
+ self._command = sys.executable + f" -m pip install -U robotframework-ride=={uversion}"
RIDEDialog.__init__(self, title="Update available", size=(400, 400),
style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT)
# set Left to Right direction (while we don't have localization)
@@ -117,10 +213,17 @@ def __init__(self, uversion, url, settings):
"do not need automatic update checks")
checkbox.Bind(wx.EVT_CHECKBOX, handler=self.on_checkbox_change)
sizer.Add(checkbox)
+ hsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
button = ButtonWithHandler(self, label="remind me later", handler=self.on_remind_me_later)
button.SetBackgroundColour(Colour(self.color_secondary_background))
button.SetForegroundColour(Colour(self.color_secondary_foreground))
- sizer.Add(button)
+ hsizer.Add(button)
+ hsizer.AddSpacer(50)
+ up_button = ButtonWithHandler(self, label="Upgrade Now", handler=self.on_upgrade_now)
+ up_button.SetBackgroundColour(Colour(self.color_secondary_background))
+ up_button.SetForegroundColour(Colour(self.color_secondary_foreground))
+ hsizer.Add(up_button)
+ sizer.Add(hsizer)
self.SetSizer(sizer)
self.CentreOnParent(wx.BOTH)
self.Fit()
@@ -135,3 +238,8 @@ def on_remind_me_later(self, event):
def on_checkbox_change(self, event):
self._settings[_CHECK_FOR_UPDATES_SETTING] = not event.IsChecked()
event.Skip()
+
+ def on_upgrade_now(self, event):
+ _ = event
+ _add_content_to_clipboard(self._command)
+ do_upgrade(self._command)
diff --git a/src/robotide/postinstall/__main__.py b/src/robotide/postinstall/__main__.py
index aa16cf8fd..013fdec92 100755
--- a/src/robotide/postinstall/__main__.py
+++ b/src/robotide/postinstall/__main__.py
@@ -72,11 +72,13 @@ def __init__(self, parent, message, title, ttl=10):
self.CenterOnScreen(wx.BOTH)
self.timeToLive = ttl
-
- st_msg = wx.StaticText(self, -1, message)
+ st_msg = []
+ for msg in message.split('\n'):
+ st_msg.append(wx.StaticText(self, -1, msg))
self.settimetolivemsg = wx.StaticText(self, -1, 'Closing this dialog box in %ds...' % self.timeToLive)
vbox = wx.BoxSizer(wx.VERTICAL)
- vbox.Add(st_msg, 0, wx.ALIGN_CENTER | wx.TOP, 40)
+ for sp_msg in st_msg:
+ vbox.Add(sp_msg, 0, wx.ALIGN_LEFT | wx.TOP, 10)
vbox.Add(self.settimetolivemsg, 0, wx.ALIGN_CENTER | wx.TOP, 10)
self.SetSizer(vbox)
self.SetAffirmativeId(wx.ID_OK)
diff --git a/src/robotide/ui/mainframe.py b/src/robotide/ui/mainframe.py
index 94069ca58..36b8df3ec 100644
--- a/src/robotide/ui/mainframe.py
+++ b/src/robotide/ui/mainframe.py
@@ -67,6 +67,7 @@
!Report a Problem | Open browser to SEARCH on the RIDE issue tracker
!Release notes | Shows release notes
!About | Information about RIDE
+!Check for Upgrade | Looks at PyPi for new released version
"""
ID_CustomizeToolbar = wx.ID_HIGHEST + 1
@@ -159,15 +160,16 @@ def _show_modification_prevented_error(message):
style=wx.ICON_ERROR)
def _init_ui(self):
- # self.aui_mgr.AddPane(wx.Panel(self), aui.AuiPaneInfo().CenterPane())
- # #### self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
+ """ DEBUG:
+ self.aui_mgr.AddPane(wx.Panel(self), aui.AuiPaneInfo().CenterPane())
+ self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
+ """
self.aui_mgr.AddPane(wx.Panel(self), aui.AuiPaneInfo().Name("right_pane").Right())
# set up default notebook style
self._notebook_style = (aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_WINDOWLIST_BUTTON |
aui.AUI_NB_TAB_EXTERNAL_MOVE | aui.AUI_NB_SUB_NOTEBOOK | aui.AUI_NB_SMART_TABS)
- # DEBUG self._notebook_theme = 0 (allow to select themes for notebooks)
- # self.notebook = NoteBook(self.splitter, self._application,
- # self._notebook_style)
+ # DEBUG: self._notebook_theme = 0 (allow to select themes for notebooks)
+ # DEBUG:self.notebook = NoteBook(self.splitter, self._application, self._notebook_style)
self.notebook = NoteBook(self, self._application,
self._notebook_style)
self.notebook.SetBackgroundColour(Colour(self.color_background))
@@ -504,13 +506,21 @@ def on_about(event):
dlg.ShowModal()
dlg.Destroy()
+ def on_check_for_upgrade(self, event):
+ _ = event
+ from ..application.updatenotifier import UpdateNotifierController, UpdateDialog
+ wx.CallAfter(UpdateNotifierController(self.general_settings).notify_update_if_needed,
+ UpdateDialog, ignore_check_condition=True)
+
# @staticmethod
def on_shortcut_keys(self, event):
_ = event
dialog = ShortcutKeysDialog()
- # self.aui_mgr.AddPane(dialog.GetContentWindow(),aui.AuiPaneInfo().Name("shortcuts").Caption("Shortcuts Keys").
- # CloseButton(True).RightDockable().Floatable().Float(), self.notebook)
- # self.aui_mgr.Update()
+ """ DEBUG:
+ self.aui_mgr.AddPane(dialog.GetContentWindow(),aui.AuiPaneInfo().Name("shortcuts").Caption("Shortcuts Keys")
+ .CloseButton(True).RightDockable().Floatable().Float(), self.notebook)
+ self.aui_mgr.Update()
+ """
dialog.Show()
@staticmethod
diff --git a/src/robotide/utils/versioncomparator.py b/src/robotide/utils/versioncomparator.py
index f704e89b0..d7844fa7e 100644
--- a/src/robotide/utils/versioncomparator.py
+++ b/src/robotide/utils/versioncomparator.py
@@ -13,7 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from pkg_resources import parse_version
+try:
+ from pkg_resources import parse_version
+except ImportError:
+ try:
+ from packaging.version import parse as parse_version
+ except ImportError as e:
+ print("RIDE cannot verify versions upgrade because of missing packages."
+ "You can install missing package with:\npip install packaging\nor\npip install setuptools")
+ raise e
def cmp_versions(version1, version2):
diff --git a/src/robotide/version.py b/src/robotide/version.py
index a92a18e61..db3167d90 100644
--- a/src/robotide/version.py
+++ b/src/robotide/version.py
@@ -14,4 +14,4 @@
# limitations under the License.
#
# Automatically generated by `tasks.py`.
-VERSION = 'v2.0.8dev19'
+VERSION = 'v2.0.8dev20'
diff --git a/utest/application/test_updatenotifier.py b/utest/application/test_updatenotifier.py
index 485be03e3..9ed9e1575 100644
--- a/utest/application/test_updatenotifier.py
+++ b/utest/application/test_updatenotifier.py
@@ -16,12 +16,14 @@
import unittest
import time
import urllib
+import wx
from robotide.application.updatenotifier import UpdateNotifierController
CHECKFORUPDATES = 'check for updates'
LASTUPDATECHECK = 'last update check'
+
class UpdateNotifierTestCase(unittest.TestCase):
def setUp(self):
@@ -113,15 +115,16 @@ def test_first_run_sets_settings_correctly_and_finds_an_update(self):
self.assertTrue(self._callback_called)
def test_checking_timeouts(self):
+ app = wx.App()
settings = self.internal_settings()
ctrl = UpdateNotifierController(settings)
def throw_timeout_error():
- raise urllib.URLError('timeout')
+ raise urllib.error.URLError('timeout')
ctrl._get_newest_version = throw_timeout_error
ctrl.notify_update_if_needed(self._callback)
- self.assertTrue(settings[LASTUPDATECHECK] > time.time() - 1)
+ self.assertTrue(settings[LASTUPDATECHECK] > time.time() - 10) # The dialog timeout in 10 seconds
self.assertFalse(self._callback_called)
def test_download_url_checking_timeouts(self):
@@ -132,7 +135,7 @@ def test_download_url_checking_timeouts(self):
def throw_timeout_error(*args):
_ = args
- raise urllib.URLError('timeout')
+ raise urllib.error.URLError('timeout')
ctrl._get_download_url = throw_timeout_error
ctrl.notify_update_if_needed(self._callback)
@@ -155,6 +158,33 @@ def test_server_returns_older_version(self):
self.assertTrue(settings[CHECKFORUPDATES])
self.assertFalse(self._callback_called)
+ def test_forced_check_released(self):
+ app = wx.App()
+ settings = self.internal_settings()
+ ctrl = self._update_notifier_controller(settings, '0.43.0', '0.43.1')
+ ctrl.notify_update_if_needed(self._callback, ignore_check_condition=True)
+ self.assertTrue(settings[LASTUPDATECHECK] > time.time() - 9) # The dialog timeout in 10 seconds
+ self.assertTrue(settings[CHECKFORUPDATES])
+ self.assertTrue(self._callback_called)
+
+ def test_forced_check_development(self):
+ app = wx.App()
+ settings = self.internal_settings()
+ ctrl = self._update_notifier_controller(settings, '0.44dev12', '0.44.dev14')
+ ctrl.notify_update_if_needed(self._callback, ignore_check_condition=True)
+ self.assertTrue(settings[LASTUPDATECHECK] > time.time() - 10) # The dialog timeout in 10 seconds
+ self.assertTrue(settings[CHECKFORUPDATES])
+ self.assertTrue(self._callback_called)
+
+ def test_forced_check_development_ok(self):
+ app = wx.App()
+ settings = self.internal_settings()
+ ctrl = self._update_notifier_controller(settings, '0.44dev12', '0.44.dev12')
+ ctrl.notify_update_if_needed(self._callback, ignore_check_condition=False)
+ self.assertTrue(settings[LASTUPDATECHECK] > time.time() - 10) # The dialog timeout in 10 seconds
+ self.assertTrue(settings[CHECKFORUPDATES])
+ self.assertFalse(self._callback_called)
+
if __name__ == '__main__':
unittest.main()