From 10b5584ca68457dd25659a3273d9375602667c19 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 10 Oct 2018 16:24:13 +0200 Subject: [PATCH] [CURA-5483] Support more than just the UM3(E) for the firmware-update-check (add S5 only for now). --- .../FirmwareUpdateChecker.py | 22 +++- .../FirmwareUpdateCheckerJob.py | 103 ++++++++++++++---- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index f01e8cb276..80a954c1cc 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -12,23 +12,34 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack -from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob +from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, MachineId, get_settings_key_for_machine 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?utm_source=cura&utm_medium=software&utm_campaign=resources" + UM_NEW_URL_TEMPLATE = "http://software.ultimaker.com/releases/firmware/{0}/stable/version.txt" + VERSION_URLS_PER_MACHINE = \ + { + MachineId.UM3: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3.value)], + MachineId.UM3E: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3E.value)], + MachineId.S5: [UM_NEW_URL_TEMPLATE.format(MachineId.S5.value)] + } + # The 'new'-style URL is the only way to check for S5 firmware, + # and in the future, the UM3 line will also switch over, but for now the old 'JEDI'-style URL is still needed. + # TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer. + # See also the to-do in FirmWareCheckerJob. 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 - Application.getInstance().getPreferences().addPreference("info/latest_checked_firmware", "") + # checked for each printer. + for machine_id in MachineId: + Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the # 'check for updates' option @@ -68,7 +79,8 @@ class FirmwareUpdateChecker(Extension): Logger.log("i", "A firmware update check is already running, do nothing.") return - self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL, + self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, + urls = self.VERSION_URLS_PER_MACHINE, callback = self._onActionTriggered, set_download_url_callback = self._onSetDownloadUrl) self._check_job.start() diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index eadacf2c02..658e820b4b 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -1,10 +1,13 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from enum import Enum, unique + from UM.Application import Application from UM.Message import Message from UM.Logger import Logger from UM.Job import Job +from UM.Version import Version import urllib.request import codecs @@ -12,49 +15,104 @@ import codecs from UM.i18n import i18nCatalog i18n_catalog = i18nCatalog("cura") +# For UM-machines, these need to match the unique firmware-ID (also used in the URLs), i.o.t. only define in one place. +@unique +class MachineId(Enum): + UM3 = 9066 + UM3E = 9511 + S5 = 9051 + + +def get_settings_key_for_machine(machine_id: MachineId) -> str: + return "info/latest_checked_firmware_for_{0}".format(machine_id.value) + + +def default_parse_version_response(response: str) -> Version: + raw_str = response.split('\n', 1)[0].rstrip() + return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different. + ## 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, callback = None, set_download_url_callback = None): + MACHINE_PER_NAME = \ + { + "ultimaker 3": MachineId.UM3, + "ultimaker 3 extended": MachineId.UM3E, + "ultimaker s5": MachineId.S5 + } + PARSE_VERSION_URL_PER_MACHINE = \ + { + MachineId.UM3: default_parse_version_response, + MachineId.UM3E: default_parse_version_response, + MachineId.S5: default_parse_version_response + } + REDIRECT_USER_PER_MACHINE = \ + { + MachineId.UM3: "https://ultimaker.com/en/resources/20500-upgrade-firmware", + MachineId.UM3E: "https://ultimaker.com/en/resources/20500-upgrade-firmware", + MachineId.S5: "https://ultimaker.com/en/resources/20500-upgrade-firmware" + } + # TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer. + + def __init__(self, container=None, silent=False, urls=None, callback=None, set_download_url_callback=None): super().__init__() self._container = container self.silent = silent - self._url = url + self._urls = urls self._callback = callback self._set_download_url_callback = set_download_url_callback + application_name = Application.getInstance().getApplicationName() + application_version = Application.getInstance().getVersion() + self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)} + + def getUrlResponse(self, url: str) -> str: + request = urllib.request.Request(url, headers=self._headers) + current_version_file = urllib.request.urlopen(request) + reader = codecs.getreader("utf-8") + + return reader(current_version_file).read(firstline=True) + + def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: + max_version = Version([0, 0, 0]) + + machine_urls = self._urls.get(machine_id) + parse_function = self.PARSE_VERSION_URL_PER_MACHINE.get(machine_id) + if machine_urls is not None and parse_function is not None: + for url in machine_urls: + version = parse_function(self.getUrlResponse(url)) + if version > max_version: + max_version = version + + if max_version < Version([0, 0, 1]): + Logger.log('w', "MachineID {0} not handled!".format(repr(machine_id))) + + return max_version + def run(self): - if not self._url: + if not self._urls or self._urls is None: Logger.log("e", "Can not check for a new release. URL not set!") return try: - application_name = Application.getInstance().getApplicationName() - headers = {"User-Agent": "%s - %s" % (application_name, Application.getInstance().getVersion())} - request = urllib.request.Request(self._url, headers = headers) - 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"]: - Logger.log("i", "You have a UM3 in printer list. Let's check the firmware!") + machine_id = self.MACHINE_PER_NAME.get(machine_name.lower()) + if machine_id is not None: + Logger.log("i", "You have a {0} in the printer list. Let's check the firmware!".format(machine_name)) - # 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() + current_version = self.getCurrentVersionForMachine(machine_id) # If it is the first time the version is checked, the checked_version is '' - checked_version = Application.getInstance().getPreferences().getValue("info/latest_checked_firmware") + setting_key_str = get_settings_key_for_machine(machine_id) + checked_version = Application.getInstance().getPreferences().getValue(setting_key_str) # 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 - Application.getInstance().getPreferences().setValue("info/latest_checked_firmware", current_version) + Application.getInstance().getPreferences().setValue(setting_key_str, current_version) Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version) # The first time we want to store the current version, the notification will not be shown, @@ -78,12 +136,17 @@ class FirmwareUpdateCheckerJob(Job): button_style=Message.ActionButtonStyle.LINK, button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) - # If we do this in a cool way, the download url should be available in the JSON file if self._set_download_url_callback: - self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware") + redirect = self.REDIRECT_USER_PER_MACHINE.get(machine_id) + if redirect is not None: + self._set_download_url_callback(redirect) + else: + Logger.log('w', "No callback-url for firmware of {0}".format(repr(machine_id))) message.actionTriggered.connect(self._callback) message.show() + else: + Logger.log('i', "No machine with name {0} in list of firmware to check.".format(repr(machine_id))) except Exception as e: Logger.log("w", "Failed to check for new version: %s", e)