Browse Source

Merge branch '4.5' of github.com:Ultimaker/Cura

Jaime van Kessel 5 years ago
parent
commit
238dd733ea

+ 4 - 1
cura/Machines/Models/IntentModel.py

@@ -114,7 +114,10 @@ class IntentModel(ListModel):
                 Logger.log("w", "Could not find the variant %s", active_variant_name)
                 Logger.log("w", "Could not find the variant %s", active_variant_name)
                 continue
                 continue
             active_variant_node = machine_node.variants[active_variant_name]
             active_variant_node = machine_node.variants[active_variant_name]
-            active_material_node = active_variant_node.materials[extruder.material.getMetaDataEntry("base_file")]
+            active_material_node = active_variant_node.materials.get(extruder.material.getMetaDataEntry("base_file"))
+            if active_material_node is None:
+                Logger.log("w", "Could not find the material %s", extruder.material.getMetaDataEntry("base_file"))
+                continue
             nodes.add(active_material_node)
             nodes.add(active_material_node)
 
 
         return nodes
         return nodes

+ 18 - 12
cura/OAuth2/LocalAuthorizationServer.py

@@ -1,13 +1,18 @@
-# Copyright (c) 2019 Ultimaker B.V.
+# Copyright (c) 2020 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 threading
 import threading
-from typing import Optional, Callable, Any, TYPE_CHECKING
+from typing import Any, Callable, Optional, TYPE_CHECKING
 
 
 from UM.Logger import Logger
 from UM.Logger import Logger
 
 
-from cura.OAuth2.AuthorizationRequestServer import AuthorizationRequestServer
-from cura.OAuth2.AuthorizationRequestHandler import AuthorizationRequestHandler
+got_server_type = False
+try:
+    from cura.OAuth2.AuthorizationRequestServer import AuthorizationRequestServer
+    from cura.OAuth2.AuthorizationRequestHandler import AuthorizationRequestHandler
+    got_server_type = True
+except PermissionError:  # Bug in http.server: Can't access MIME types. This will prevent the user from logging in. See Sentry bug Cura-3Q.
+    Logger.error("Can't start a server due to a PermissionError when starting the http.server.")
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from cura.OAuth2.Models import AuthenticationResponse
     from cura.OAuth2.Models import AuthenticationResponse
@@ -50,15 +55,16 @@ class LocalAuthorizationServer:
         Logger.log("d", "Starting local web server to handle authorization callback on port %s", self._web_server_port)
         Logger.log("d", "Starting local web server to handle authorization callback on port %s", self._web_server_port)
 
 
         # Create the server and inject the callback and code.
         # Create the server and inject the callback and code.
-        self._web_server = AuthorizationRequestServer(("0.0.0.0", self._web_server_port), AuthorizationRequestHandler)
-        self._web_server.setAuthorizationHelpers(self._auth_helpers)
-        self._web_server.setAuthorizationCallback(self._auth_state_changed_callback)
-        self._web_server.setVerificationCode(verification_code)
-        self._web_server.setState(state)
+        if got_server_type:
+            self._web_server = AuthorizationRequestServer(("0.0.0.0", self._web_server_port), AuthorizationRequestHandler)
+            self._web_server.setAuthorizationHelpers(self._auth_helpers)
+            self._web_server.setAuthorizationCallback(self._auth_state_changed_callback)
+            self._web_server.setVerificationCode(verification_code)
+            self._web_server.setState(state)
 
 
-        # Start the server on a new thread.
-        self._web_server_thread = threading.Thread(None, self._web_server.serve_forever, daemon = self._daemon)
-        self._web_server_thread.start()
+            # Start the server on a new thread.
+            self._web_server_thread = threading.Thread(None, self._web_server.serve_forever, daemon = self._daemon)
+            self._web_server_thread.start()
 
 
     ##  Stops the web server if it was running. It also does some cleanup.
     ##  Stops the web server if it was running. It also does some cleanup.
     def stop(self) -> None:
     def stop(self) -> None:

+ 5 - 4
cura_app.py

@@ -31,10 +31,11 @@ known_args = vars(parser.parse_known_args()[0])
 if with_sentry_sdk:
 if with_sentry_sdk:
     sentry_env = "unknown"  # Start off with a "IDK"
     sentry_env = "unknown"  # Start off with a "IDK"
     if hasattr(sys, "frozen"):
     if hasattr(sys, "frozen"):
-        sentry_env = "production"  # A frozen build is a "real" distribution.
-    elif ApplicationMetadata.CuraVersion == "master":
-        sentry_env = "development"
-    elif "beta" in ApplicationMetadata.CuraVersion or "BETA" in ApplicationMetadata.CuraVersion:
+        sentry_env = "production"  # A frozen build has the posibility to be a "real" distribution.
+
+    if ApplicationMetadata.CuraVersion == "master":
+        sentry_env = "development"  # Master is always a development version.
+    elif ApplicationMetadata.CuraVersion in ["beta", "BETA"]:
         sentry_env = "beta"
         sentry_env = "beta"
     try:
     try:
         if ApplicationMetadata.CuraVersion.split(".")[2] == "99":
         if ApplicationMetadata.CuraVersion.split(".")[2] == "99":

+ 3 - 0
plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py

@@ -47,7 +47,10 @@ class WindowsRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin):
     def checkRemovableDrives(self):
     def checkRemovableDrives(self):
         drives = {}
         drives = {}
 
 
+        # The currently available disk drives, e.g.: bitmask = ...1100 <-- ...DCBA
         bitmask = ctypes.windll.kernel32.GetLogicalDrives()
         bitmask = ctypes.windll.kernel32.GetLogicalDrives()
+        # Since we are ignoring drives A and B, the bitmask has has to shift twice to the right
+        bitmask >>= 2
         # Check possible drive letters, from C to Z
         # Check possible drive letters, from C to Z
         # Note: using ascii_uppercase because we do not want this to change with locale!
         # Note: using ascii_uppercase because we do not want this to change with locale!
         # Skip A and B, since those drives are typically reserved for floppy disks.
         # Skip A and B, since those drives are typically reserved for floppy disks.

+ 1 - 1
plugins/Toolbox/src/CloudSync/CloudPackageChecker.py

@@ -83,7 +83,7 @@ class CloudPackageChecker(QObject):
         package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages))
         package_discrepancy = list(set(user_subscribed_packages).difference(user_installed_packages))
         if package_discrepancy:
         if package_discrepancy:
             self._model.addDiscrepancies(package_discrepancy)
             self._model.addDiscrepancies(package_discrepancy)
-            self._model.initialize(subscribed_packages_payload)
+            self._model.initialize(self._package_manager, subscribed_packages_payload)
             self._handlePackageDiscrepancies()
             self._handlePackageDiscrepancies()
 
 
     def _handlePackageDiscrepancies(self) -> None:
     def _handlePackageDiscrepancies(self) -> None:

+ 22 - 2
plugins/Toolbox/src/CloudSync/LicensePresenter.py

@@ -4,6 +4,7 @@ from typing import Dict, Optional, List, Any
 
 
 from PyQt5.QtCore import QObject, pyqtSlot
 from PyQt5.QtCore import QObject, pyqtSlot
 
 
+from UM.Logger import Logger
 from UM.PackageManager import PackageManager
 from UM.PackageManager import PackageManager
 from UM.Signal import Signal
 from UM.Signal import Signal
 from cura.CuraApplication import CuraApplication
 from cura.CuraApplication import CuraApplication
@@ -12,12 +13,19 @@ from UM.i18n import i18nCatalog
 from .LicenseModel import LicenseModel
 from .LicenseModel import LicenseModel
 
 
 
 
-## Call present() to show a licenseDialog for a set of packages
-#  licenseAnswers emits a list of Dicts containing answers when the user has made a choice for all provided packages
 class LicensePresenter(QObject):
 class LicensePresenter(QObject):
+    """Presents licenses for a set of packages for the user to accept or reject.
+
+    Call present() exactly once to show a licenseDialog for a set of packages
+    Before presenting another set of licenses, create a new instance using resetCopy().
+
+    licenseAnswers emits a list of Dicts containing answers when the user has made a choice for all provided packages.
+    """
 
 
     def __init__(self, app: CuraApplication) -> None:
     def __init__(self, app: CuraApplication) -> None:
         super().__init__()
         super().__init__()
+        self._presented = False
+        """Whether present() has been called and state is expected to be initialized"""
         self._catalog = i18nCatalog("cura")
         self._catalog = i18nCatalog("cura")
         self._dialog = None  # type: Optional[QObject]
         self._dialog = None  # type: Optional[QObject]
         self._package_manager = app.getPackageManager()  # type: PackageManager
         self._package_manager = app.getPackageManager()  # type: PackageManager
@@ -39,6 +47,10 @@ class LicensePresenter(QObject):
     # \param plugin_path: Root directory of the Toolbox plugin
     # \param plugin_path: Root directory of the Toolbox plugin
     # \param packages: Dict[package id, file path]
     # \param packages: Dict[package id, file path]
     def present(self, plugin_path: str, packages: Dict[str, Dict[str, str]]) -> None:
     def present(self, plugin_path: str, packages: Dict[str, Dict[str, str]]) -> None:
+        if self._presented:
+            Logger.error("{clazz} is single-use. Create a new {clazz} instead", clazz=self.__class__.__name__)
+            return
+
         path = os.path.join(plugin_path, self._compatibility_dialog_path)
         path = os.path.join(plugin_path, self._compatibility_dialog_path)
 
 
         self._initState(packages)
         self._initState(packages)
@@ -56,6 +68,14 @@ class LicensePresenter(QObject):
             }
             }
             self._dialog = self._app.createQmlComponent(path, context_properties)
             self._dialog = self._app.createQmlComponent(path, context_properties)
         self._presentCurrentPackage()
         self._presentCurrentPackage()
+        self._presented = True
+
+    def resetCopy(self) -> "LicensePresenter":
+        """Clean up and return a new copy with the same settings such as app"""
+        if self._dialog:
+            self._dialog.close()
+        self.licenseAnswers.disconnectAll()
+        return LicensePresenter(self._app)
 
 
     @pyqtSlot()
     @pyqtSlot()
     def onLicenseAccepted(self) -> None:
     def onLicenseAccepted(self) -> None:

+ 9 - 8
plugins/Toolbox/src/CloudSync/SubscribedPackagesModel.py

@@ -2,9 +2,12 @@
 # Cura is released under the terms of the LGPLv3 or higher.
 # Cura is released under the terms of the LGPLv3 or higher.
 
 
 from PyQt5.QtCore import Qt, pyqtProperty, pyqtSlot
 from PyQt5.QtCore import Qt, pyqtProperty, pyqtSlot
+
+from UM.PackageManager import PackageManager
 from UM.Qt.ListModel import ListModel
 from UM.Qt.ListModel import ListModel
+from UM.Version import Version
+
 from cura import ApplicationMetadata
 from cura import ApplicationMetadata
-from UM.Logger import Logger
 from typing import List, Dict, Any
 from typing import List, Dict, Any
 
 
 
 
@@ -46,7 +49,7 @@ class SubscribedPackagesModel(ListModel):
     def getIncompatiblePackages(self) -> List[str]:
     def getIncompatiblePackages(self) -> List[str]:
         return [package["package_id"] for package in self._items if not package["is_compatible"]]
         return [package["package_id"] for package in self._items if not package["is_compatible"]]
 
 
-    def initialize(self, subscribed_packages_payload: List[Dict[str, Any]]) -> None:
+    def initialize(self, package_manager: PackageManager, subscribed_packages_payload: List[Dict[str, Any]]) -> None:
         self._items.clear()
         self._items.clear()
         for item in subscribed_packages_payload:
         for item in subscribed_packages_payload:
             if item["package_id"] not in self._discrepancies:
             if item["package_id"] not in self._discrepancies:
@@ -59,15 +62,13 @@ class SubscribedPackagesModel(ListModel):
                 "md5_hash": item["md5_hash"],
                 "md5_hash": item["md5_hash"],
                 "is_dismissed": False,
                 "is_dismissed": False,
             }
             }
-            if self._sdk_version not in item["sdk_versions"]:
-                package.update({"is_compatible": False})
-            else:
-                package.update({"is_compatible": True})
+
+            compatible = any(package_manager.isPackageCompatible(Version(version)) for version in item["sdk_versions"])
+            package.update({"is_compatible": compatible})
+
             try:
             try:
                 package.update({"icon_url": item["icon_url"]})
                 package.update({"icon_url": item["icon_url"]})
             except KeyError:  # There is no 'icon_url" in the response payload for this package
             except KeyError:  # There is no 'icon_url" in the response payload for this package
                 package.update({"icon_url": ""})
                 package.update({"icon_url": ""})
             self._items.append(package)
             self._items.append(package)
         self.setItems(self._items)
         self.setItems(self._items)
-
-

+ 2 - 0
plugins/Toolbox/src/CloudSync/SyncOrchestrator.py

@@ -72,6 +72,8 @@ class SyncOrchestrator(Extension):
             self._showErrorMessage(message)
             self._showErrorMessage(message)
 
 
         plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath(self.getPluginId()))
         plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath(self.getPluginId()))
+        self._license_presenter = self._license_presenter.resetCopy()
+        self._license_presenter.licenseAnswers.connect(self._onLicenseAnswers)
         self._license_presenter.present(plugin_path, success_items)
         self._license_presenter.present(plugin_path, success_items)
 
 
     # Called when user has accepted / declined all licenses for the downloaded packages
     # Called when user has accepted / declined all licenses for the downloaded packages