Browse Source

[CURA-5483] Support more than just the UM3(E) for the firmware-update-check (add S5 only for now).

Remco Burema 6 years ago
parent
commit
10b5584ca6

+ 17 - 5
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()

+ 83 - 20
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)