diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 459d29265d..1736bb228a 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -1,18 +1,20 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import json, os from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices from UM.Extension import Extension from UM.Application import Application from UM.Logger import Logger +from UM.PluginRegistry import PluginRegistry from UM.i18n import i18nCatalog from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack -from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, MachineId, get_settings_key_for_machine +from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, get_settings_key_for_machine i18n_catalog = i18nCatalog("cura") @@ -20,38 +22,23 @@ i18n_catalog = i18nCatalog("cura") # 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 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 Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True) if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) + self._late_init = True # Init some things after creation, since we need the path from the plugin-mgr. self._download_url = None self._check_job = None self._name_cache = [] ## Callback for the message that is spawned when there is a new version. + # TODO: Set the right download URL for each message! def _onActionTriggered(self, message, action): if action == "download": if self._download_url is not None: @@ -68,6 +55,25 @@ class FirmwareUpdateChecker(Extension): def _onJobFinished(self, *args, **kwargs): self._check_job = None + def lateInit(self): + self._late_init = False + + # Open the .json file with the needed lookup-lists for each machine(/model) and retrieve 'raw' json. + self._machines_json = None + json_path = os.path.join(PluginRegistry.getInstance().getPluginPath("FirmwareUpdateChecker"), + "resources/machines.json") + with open(json_path, "r", encoding="utf-8") as json_file: + self._machines_json = json.load(json_file).get("machines") + if self._machines_json is None: + Logger.log('e', "Missing or inaccessible: {0}".format(json_path)) + return + + # Initialize the Preference called `latest_checked_firmware` that stores the last version + # checked for each printer. + for machine_json in self._machines_json: + machine_id = machine_json.get("id") + Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "") + ## 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. @@ -75,13 +81,16 @@ class FirmwareUpdateChecker(Extension): # \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): + if self._late_init: + self.lateInit() + container_name = container.definition.getName() if container_name in self._name_cache: return self._name_cache.append(container_name) self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, - urls = self.VERSION_URLS_PER_MACHINE, + machines_json = self._machines_json, 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 41710e7e86..336b954f5e 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -16,16 +16,9 @@ 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 get_settings_key_for_machine(machine_id: int) -> str: + return "info/latest_checked_firmware_for_{0}".format(machine_id) def default_parse_version_response(response: str) -> Version: @@ -39,31 +32,39 @@ class FirmwareUpdateCheckerJob(Job): STRING_EPSILON_VERSION = "0.0.1" ZERO_VERSION = Version(STRING_ZERO_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION) - 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. + JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response} - def __init__(self, container=None, silent=False, urls=None, callback=None, set_download_url_callback=None): + def __init__(self, container=None, silent=False, machines_json=None, callback=None, set_download_url_callback=None): super().__init__() self._container = container self.silent = silent - self._urls = urls + + # Parse all the needed lookup-tables from the '.json' file(s) in the resources folder. + # TODO: This should not be here when the merge to master is done, as it will be repeatedly recreated. + # It should be a separate object this constructor receives instead. + self._machine_ids = [] + self._machine_per_name = {} + self._parse_version_url_per_machine = {} + self._check_urls_per_machine = {} + self._redirect_user_per_machine = {} + try: + for machine_json in machines_json: + machine_id = machine_json.get("id") + machine_name = machine_json.get("name") + self._machine_ids.append(machine_id) + self._machine_per_name[machine_name] = machine_id + version_parse_function = self.JSON_NAME_TO_VERSION_PARSE_FUNCTION.get(machine_json.get("version_parser")) + if version_parse_function is None: + Logger.log('w', "No version-parse-function specified for machine {0}.".format(machine_name)) + version_parse_function = default_parse_version_response # Use default instead if nothing is found. + self._parse_version_url_per_machine[machine_id] = version_parse_function + self._check_urls_per_machine[machine_id] = [] # Multiple check-urls: see '_comment' in the .json file. + for check_url in machine_json.get("check_urls"): + self._check_urls_per_machine[machine_id].append(check_url) + self._redirect_user_per_machine[machine_id] = machine_json.get("update_url") + except: + Logger.log('e', "Couldn't parse firmware-update-check loopup-lists from file.") + self._callback = callback self._set_download_url_callback = set_download_url_callback @@ -82,11 +83,11 @@ class FirmwareUpdateCheckerJob(Job): return result - def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: + def getCurrentVersionForMachine(self, machine_id: int) -> Version: max_version = self.ZERO_VERSION - machine_urls = self._urls.get(machine_id) - parse_function = self.PARSE_VERSION_URL_PER_MACHINE.get(machine_id) + machine_urls = self._check_urls_per_machine.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)) @@ -99,7 +100,7 @@ class FirmwareUpdateCheckerJob(Job): return max_version def run(self): - if not self._urls or self._urls is None: + if not self._machine_ids or self._machine_ids is None: Logger.log("e", "Can not check for a new release. URL not set!") return @@ -112,7 +113,7 @@ class FirmwareUpdateCheckerJob(Job): machine_name = self._container.definition.getName() # If it is not None, then we compare between the checked_version and the current_version - machine_id = self.MACHINE_PER_NAME.get(machine_name.lower()) + 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)) @@ -150,7 +151,7 @@ class FirmwareUpdateCheckerJob(Job): # If we do this in a cool way, the download url should be available in the JSON file if self._set_download_url_callback: - redirect = self.REDIRECT_USER_PER_MACHINE.get(machine_id) + redirect = self._redirect_user_per_machine.get(machine_id) if redirect is not None: self._set_download_url_callback(redirect) else: diff --git a/plugins/FirmwareUpdateChecker/resources/machines.json b/plugins/FirmwareUpdateChecker/resources/machines.json new file mode 100644 index 0000000000..5dc9aadbbf --- /dev/null +++ b/plugins/FirmwareUpdateChecker/resources/machines.json @@ -0,0 +1,36 @@ +{ + "_comment": "Multiple 'update_urls': 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.", + + "machines": + [ + { + "id": 9066, + "name": "ultimaker 3", + "check_urls": + [ + "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", + "http://software.ultimaker.com/releases/firmware/9066/stable/version.txt" + ], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", + "version_parser": "default" + }, + { + "id": 9511, + "name": "ultimaker 3 extended", + "check_urls": + [ + "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources", + "http://software.ultimaker.com/releases/firmware/9511/stable/version.txt" + ], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", + "version_parser": "default" + }, + { + "id": 9051, + "name": "ultimaker s5", + "check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"], + "update_url": "https://ultimaker.com/en/resources/20500-upgrade-firmware", + "version_parser": "default" + } + ] +}