diff --git a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py index 3566cda69f..18e5fcf883 100644 --- a/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevicePlugin.py @@ -1,19 +1,18 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import os import time import json +from queue import Queue +from threading import Event, Thread -from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot +from PyQt5.QtCore import QObject, pyqtSlot from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices -from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager, QNetworkReply -from PyQt5.QtQml import QQmlComponent, QQmlContext +from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager from UM.Application import Application from UM.Logger import Logger from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin -from UM.PluginRegistry import PluginRegistry from UM.Preferences import Preferences from UM.Signal import Signal, signalemitter from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo # type: ignore @@ -57,6 +56,16 @@ class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin): self._network_requests_buffer = {} # store api responses until data is complete + # The zeroconf service changed requests are handled in a separate thread, so we can re-schedule the requests + # which fail to get detailed service info. + # Any new or re-scheduled requests will be appended to the request queue, and the handling thread will pick + # them up and process them. + self._service_changed_request_queue = Queue() + self._service_changed_request_event = Event() + self._service_changed_request_thread = Thread(target = self._handleOnServiceChangedRequests, + daemon = True) + self._service_changed_request_thread.start() + addPrinterSignal = Signal() removePrinterSignal = Signal() printerListChanged = Signal() @@ -76,7 +85,7 @@ class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin): # After network switching, one must make a new instance of Zeroconf # On windows, the instance creation is very fast (unnoticable). Other platforms? self._zero_conf = Zeroconf() - self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._onServiceChanged]) + self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._appendServiceChangedRequest]) # Look for manual instances from preference for address in self._manual_instances: @@ -254,7 +263,8 @@ class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin): else: self.getOutputDeviceManager().removeOutputDevice(key) - ## Handler for zeroConf detection + ## Handler for zeroConf detection. + # Return True or False indicating if the process succeeded. def _onServiceChanged(self, zeroconf, service_type, name, state_change): if state_change == ServiceStateChange.Added: Logger.log("d", "Bonjour service added: %s" % name) @@ -272,7 +282,7 @@ class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin): # Request more data if info is not complete if not info.address: Logger.log("d", "Trying to get address of %s", name) - info = zeroconf.get_service_info(service_type, name) + info = zeroconf.get_service_info(service_type, name, 0) if info: type_of_device = info.properties.get(b"type", None) @@ -284,11 +294,50 @@ class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin): Logger.log("w", "The type of the found device is '%s', not 'printer'! Ignoring.." % type_of_device ) else: Logger.log("w", "Could not get information about %s" % name) + return False elif state_change == ServiceStateChange.Removed: Logger.log("d", "Bonjour service removed: %s" % name) self.removePrinterSignal.emit(str(name)) + return True + + ## Appends a service changed request so later the handling thread will pick it up and processes it. + def _appendServiceChangedRequest(self, zeroconf, service_type, name, state_change): + # append the request and set the event so the event handling thread can pick it up + item = (zeroconf, service_type, name, state_change) + self._service_changed_request_queue.put(item) + self._service_changed_request_event.set() + + def _handleOnServiceChangedRequests(self): + while True: + # wait for the event to be set + self._service_changed_request_event.wait(timeout = 5.0) + # stop if the application is shutting down + if Application.getInstance().isShuttingDown(): + return + + self._service_changed_request_event.clear() + + # handle all pending requests + reschedule_requests = [] # a list of requests that have failed so later they will get re-scheduled + while not self._service_changed_request_queue.empty(): + request = self._service_changed_request_queue.get() + zeroconf, service_type, name, state_change = request + try: + result = self._onServiceChanged(zeroconf, service_type, name, state_change) + if not result: + reschedule_requests.append(request) + except Exception: + Logger.logException("e", "Failed to get service info for [%s] [%s], the request will be rescheduled", + service_type, name) + reschedule_requests.append(request) + + # re-schedule the failed requests if any + if reschedule_requests: + for request in reschedule_requests: + self._service_changed_request_queue.put(request) + @pyqtSlot() def openControlPanel(self): Logger.log("d", "Opening print jobs web UI...")