diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py new file mode 100644 index 0000000000..1c099705b1 --- /dev/null +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -0,0 +1,49 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from UM.Extension import Extension +from UM.Preferences import Preferences +from UM.Logger import Logger +from UM.i18n import i18nCatalog +from cura.Settings.GlobalStack import GlobalStack + +from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob +from UM.Settings.ContainerRegistry import ContainerRegistry + +i18n_catalog = i18nCatalog("cura") + + +## This Extension checks for new versions of the firmware based on the latest checked version number. +# The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy +# to change it to work for other applications. +class FirmwareUpdateChecker(Extension): + JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version" + + def __init__(self): + super().__init__() + + # Initialize the Preference called `latest_checked_firmware` that stores the last version + # checked for the UM3. In the future if we need to check other printers' firmware + Preferences.getInstance().addPreference("info/latest_checked_firmware", "") + + # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the + # 'check for updates' option + Preferences.getInstance().addPreference("info/automatic_update_check", True) + if Preferences.getInstance().getValue("info/automatic_update_check"): + ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) + + def _onContainerAdded(self, container): + # Only take care when a new GlobalStack was added + if isinstance(container, GlobalStack): + Logger.log("i", "You have a '%s' in printer list. Let's check the firmware!", container.getId()) + self.checkFirmwareVersion(container, True) + + ## Connect with software.ultimaker.com, load latest.version and check version info. + # If the version info is different from the current version, spawn a message to + # allow the user to download it. + # + # \param silent type(boolean) Suppresses messages other than "new version found" messages. + # This is used when checking for a new firmware version at startup. + def checkFirmwareVersion(self, container = None, silent = False): + job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL) + job.start() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py new file mode 100644 index 0000000000..108cfa4c0d --- /dev/null +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -0,0 +1,86 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from UM.Preferences import Preferences +from UM.Application import Application +from UM.Message import Message +from UM.Logger import Logger +from UM.Job import Job + +import urllib.request +import codecs + +from PyQt5.QtCore import QUrl +from PyQt5.QtGui import QDesktopServices + +from UM.i18n import i18nCatalog +i18n_catalog = i18nCatalog("cura") + + +## This job checks if there is an update available on the provided URL. +class FirmwareUpdateCheckerJob(Job): + def __init__(self, container = None, silent = False, url = None): + super().__init__() + self._container = container + self.silent = silent + self._url = url + self._download_url = None # If an update was found, the download_url will be set to the location of the new version. + + ## Callback for the message that is spawned when there is a new version. + def actionTriggered(self, message, action): + if action == "download": + if self._download_url is not None: + QDesktopServices.openUrl(QUrl(self._download_url)) + + def run(self): + self._download_url = None # Reset download ur. + if not self._url: + Logger.log("e", "Can not check for a new release. URL not set!") + return + + try: + request = urllib.request.Request(self._url) + current_version_file = urllib.request.urlopen(request) + reader = codecs.getreader("utf-8") + + # get machine name from the definition container + machine_name = self._container.definition.getName() + machine_name_parts = machine_name.lower().split(" ") + + # If it is not None, then we compare between the checked_version and the current_version + # Now we just do that if the active printer is Ultimaker 3 or Ultimaker 3 Extended or any + # other Ultimaker 3 that will come in the future + if len(machine_name_parts) >= 2 and machine_name_parts[:2] == ["ultimaker", "3"]: + # Nothing to parse, just get the string + # TODO: In the future may be done by parsing a JSON file with diferent version for each printer model + current_version = reader(current_version_file).readline().rstrip() + Logger.log("i", "Reading firmware version of %s: %s", machine_name, current_version) + + # If it is the first time the version is checked, the checked_version is None + checked_version = Preferences.getInstance().getValue("info/latest_checked_firmware") + + # If the checked_version is '', it's because is the first time we check firmware and in this case + # we will not show the notification, but we will store it for the next time + if (checked_version != "") and (checked_version != current_version): + message = Message(i18n_catalog.i18nc("@info", "New %s firmware available

To ensure that your " + "%s is equiped with the latest features it is recommended " + "to update the firmware regularly. This can be done on the " + "%s (when connected to the network) or via USB." + % (machine_name, machine_name, machine_name))) + message.addAction("download", i18n_catalog.i18nc("@action:button", "Download"), "[no_icon]", "[no_description]") + + # If we do this in a cool way, the download url should be available in the JSON file + self._download_url = "https://ultimaker.com/en/resources/20500-upgrade-firmware" + message.actionTriggered.connect(self.actionTriggered) + Application.getInstance().showMessage(message) + + # The first time we want to store the current version, the notification will not be shown, + # because the new version of Cura will be release before the firmware and we don't want to + # notify the user when no new firmware version is available. + Preferences.getInstance().setValue("info/latest_checked_firmware", current_version) + + except Exception as e: + Logger.log("w", "Failed to check for new version: %s", e) + if not self.silent: + Message(i18n_catalog.i18nc("@info", "Could not access update information.")).show() + return diff --git a/plugins/FirmwareUpdateChecker/__init__.py b/plugins/FirmwareUpdateChecker/__init__.py new file mode 100644 index 0000000000..b7343dc565 --- /dev/null +++ b/plugins/FirmwareUpdateChecker/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from UM.i18n import i18nCatalog + +from . import FirmwareUpdateChecker + +i18n_catalog = i18nCatalog("cura") + + +def getMetaData(): + return {} + + +def register(app): + return {"extension": FirmwareUpdateChecker.FirmwareUpdateChecker()} diff --git a/plugins/FirmwareUpdateChecker/plugin.json b/plugins/FirmwareUpdateChecker/plugin.json new file mode 100644 index 0000000000..d6a9f9fbd7 --- /dev/null +++ b/plugins/FirmwareUpdateChecker/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "Firmware Update Checker", + "author": "Ultimaker B.V.", + "version": "1.0.0", + "description": "Checks for firmware updates.", + "api": 4, + "i18n-catalog": "cura" +}