Parse the firmware-update-check lookup-tables from a (new) .json instead of hardcoded.

This commit is contained in:
Remco Burema 2018-10-11 17:16:01 +02:00
parent 12999f48c8
commit 6c2791f382
3 changed files with 101 additions and 55 deletions

View file

@ -1,18 +1,20 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import json, os
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices from PyQt5.QtGui import QDesktopServices
from UM.Extension import Extension from UM.Extension import Extension
from UM.Application import Application from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Settings.GlobalStack import GlobalStack 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") 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 # 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. # to change it to work for other applications.
class FirmwareUpdateChecker(Extension): 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): def __init__(self):
super().__init__() 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 # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the
# 'check for updates' option # 'check for updates' option
Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True) Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True)
if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): if Application.getInstance().getPreferences().getValue("info/automatic_update_check"):
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) 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._download_url = None
self._check_job = None self._check_job = None
self._name_cache = [] self._name_cache = []
## Callback for the message that is spawned when there is a new version. ## 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): def _onActionTriggered(self, message, action):
if action == "download": if action == "download":
if self._download_url is not None: if self._download_url is not None:
@ -68,6 +55,25 @@ class FirmwareUpdateChecker(Extension):
def _onJobFinished(self, *args, **kwargs): def _onJobFinished(self, *args, **kwargs):
self._check_job = None 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. ## 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 # If the version info is different from the current version, spawn a message to
# allow the user to download it. # 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. # \param silent type(boolean) Suppresses messages other than "new version found" messages.
# This is used when checking for a new firmware version at startup. # This is used when checking for a new firmware version at startup.
def checkFirmwareVersion(self, container = None, silent = False): def checkFirmwareVersion(self, container = None, silent = False):
if self._late_init:
self.lateInit()
container_name = container.definition.getName() container_name = container.definition.getName()
if container_name in self._name_cache: if container_name in self._name_cache:
return return
self._name_cache.append(container_name) self._name_cache.append(container_name)
self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent,
urls = self.VERSION_URLS_PER_MACHINE, machines_json = self._machines_json,
callback = self._onActionTriggered, callback = self._onActionTriggered,
set_download_url_callback = self._onSetDownloadUrl) set_download_url_callback = self._onSetDownloadUrl)
self._check_job.start() self._check_job.start()

View file

@ -16,16 +16,9 @@ import codecs
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura") 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: int) -> str:
def get_settings_key_for_machine(machine_id: MachineId) -> str: return "info/latest_checked_firmware_for_{0}".format(machine_id)
return "info/latest_checked_firmware_for_{0}".format(machine_id.value)
def default_parse_version_response(response: str) -> Version: def default_parse_version_response(response: str) -> Version:
@ -39,31 +32,39 @@ class FirmwareUpdateCheckerJob(Job):
STRING_EPSILON_VERSION = "0.0.1" STRING_EPSILON_VERSION = "0.0.1"
ZERO_VERSION = Version(STRING_ZERO_VERSION) ZERO_VERSION = Version(STRING_ZERO_VERSION)
EPSILON_VERSION = Version(STRING_EPSILON_VERSION) EPSILON_VERSION = Version(STRING_EPSILON_VERSION)
MACHINE_PER_NAME = \ JSON_NAME_TO_VERSION_PARSE_FUNCTION = {"default": default_parse_version_response}
{
"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): def __init__(self, container=None, silent=False, machines_json=None, callback=None, set_download_url_callback=None):
super().__init__() super().__init__()
self._container = container self._container = container
self.silent = silent 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._callback = callback
self._set_download_url_callback = set_download_url_callback self._set_download_url_callback = set_download_url_callback
@ -82,11 +83,11 @@ class FirmwareUpdateCheckerJob(Job):
return result return result
def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version: def getCurrentVersionForMachine(self, machine_id: int) -> Version:
max_version = self.ZERO_VERSION max_version = self.ZERO_VERSION
machine_urls = self._urls.get(machine_id) machine_urls = self._check_urls_per_machine.get(machine_id)
parse_function = self.PARSE_VERSION_URL_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: if machine_urls is not None and parse_function is not None:
for url in machine_urls: for url in machine_urls:
version = parse_function(self.getUrlResponse(url)) version = parse_function(self.getUrlResponse(url))
@ -99,7 +100,7 @@ class FirmwareUpdateCheckerJob(Job):
return max_version return max_version
def run(self): 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!") Logger.log("e", "Can not check for a new release. URL not set!")
return return
@ -112,7 +113,7 @@ class FirmwareUpdateCheckerJob(Job):
machine_name = self._container.definition.getName() machine_name = self._container.definition.getName()
# If it is not None, then we compare between the checked_version and the current_version # 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: 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)) 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 we do this in a cool way, the download url should be available in the JSON file
if self._set_download_url_callback: 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: if redirect is not None:
self._set_download_url_callback(redirect) self._set_download_url_callback(redirect)
else: else:

View file

@ -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"
}
]
}