예제 #1
0
class mainApp():
    def getRequest(self):
        url = QUrl("https://jsonplaceholder.typicode.com/users/1")

        self.request = QNetworkRequest(url)

        self.manager = QNetworkAccessManager()
        self.manager.get(self.request)
        self.manager.finished.connect(self.getResponse)

    def postRequest(self):
        url = QUrl("http://httpbin.org/post")

        dataDict = {}
        dataDict["fname"] = "Budi"
        dataDict["lname"] = "Anton"

        dataJson = json.dumps(dataDict)

        data = QByteArray()
        data.append(dataJson)

        self.request = QNetworkRequest(url)
        self.request.setHeader(QNetworkRequest.ContentTypeHeader,
                               "application/json")

        self.manager = QNetworkAccessManager()
        self.manager.post(self.request, data)
        self.manager.finished.connect(self.getResponse)

    def getResponse(self, reply):
        self.result = reply.readAll()
        print(str(self.result, "utf-8"))

        QApplication.quit()
예제 #2
0
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        layout = QVBoxLayout()

        self.slider = QSlider(Qt.Horizontal, self)
        self.slider.valueChanged.connect(self.doRequest)

        layout.addWidget(self.slider)

        w = QWidget()
        w.setLayout(layout)

        self.setCentralWidget(w)

        self.show()

    def doRequest(self):
        print(self.slider.value())

        data = QByteArray()

        data.append("'brightness':")
        data.append(str(self.slider.value()))

        #url = "https://httpbin.org/post"
        url = "https://postman-echo.com/post"
        req = QNetworkRequest(QUrl(url))
        req.setHeader(QNetworkRequest.ContentTypeHeader,
                      '"Content-Type": "application/json; charset=UTF-8"',)

        self.nam = QNetworkAccessManager()
        self.nam.finished.connect(self.handleResponse)
        self.nam.post(req, data)

    def handleResponse(self, reply):

        er = reply.error()
        if er == QNetworkReply.NoError:

            bytes_string = reply.readAll()
            json_array = json.loads(str(bytes_string, 'utf-8'))

            for key, value in json_array.items():
                print(key, value)

        else:
            print("Error occurred: ", er)
            print(reply.errorString())
class ShareServiceInteractor:
    def __init__(self, view):
        self.view = view
        self.buffer = QBuffer()

        self.network_manager = QNetworkAccessManager(self.view)
        self.network_manager.finished.connect(self.on_received_response)

    def on_received_response(self, reply: QNetworkReply):
        if reply.error() != QNetworkReply.NoError:
            error_msg = "Unable to create new print share: {}".format(reply.errorString())
            logging.error(error_msg)
            app_settings.app_data_writer.signals.exchange_share_failed.emit(error_msg)
            return

        share_location = reply.rawHeader(QByteArray(bytes("Location", encoding="utf-8")))
        app_settings.app_data_writer.signals.exchange_share_created.emit(share_location.data().decode())
        reply.deleteLater()
        self.buffer.close()

    def create_document(self, raw_html):
        app_config = app_settings.load_configuration()
        url: QUrl = QUrl(app_config.print_server + "/prints")
        base64_encoded = str_to_base64_encoded_bytes(raw_html)
        jdoc = {"document": bytes_to_str(base64_encoded)}
        jdoc_str = json.dumps(jdoc)
        self.buffer.setData(str_to_bytes(jdoc_str))
        network_request = QNetworkRequest(url)
        network_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
        in_progress_reply = self.network_manager.post(network_request, self.buffer)
        self.view.finished.connect(in_progress_reply.abort)
예제 #4
0
 def post(self, url, data):
     if isinstance(data, dict): data = json.dumps(data)
     if isinstance(data, str): data = QByteArray(len(data), data)
     elif isinstance(data, bytes): data = QByteArray(data)
     else:
         data = str(data)
         data = QByteArray(len(data), data)
     return QNetworkAccessManager.post(self, self._getRequest(url), data)
예제 #5
0
 def post(self, url, data):
     if isinstance(data, dict): data = json.dumps(data)
     if isinstance(data, str): data = QByteArray(len(data), data)
     elif isinstance(data, bytes): data = QByteArray(data)
     else:
         data = str(data)
         data = QByteArray(len(data), data)
     return QNetworkAccessManager.post(self, self._getRequest(url), data)
예제 #6
0
class UrlSchemeHandler(QWebEngineUrlSchemeHandler):
    AttrType = QNetworkRequest.User + 1

    def __init__(self, *args, **kwargs):
        super(UrlSchemeHandler, self).__init__(*args, **kwargs)
        self._manager = QNetworkAccessManager(self)
        self._manager.finished.connect(self.onFinished)

    def requestStarted(self, request):
        # 拦截
        # request.fail(QWebEngineUrlRequestJob.RequestDenied)
        # print('initiator:', request.initiator())
        print('requestMethod:', request.requestMethod())
        print('requestHeaders:', request.requestHeaders())
        url = request.requestUrl()
        if url.scheme().startswith('myurl'):
            url.setScheme(url.scheme().replace('myurl', 'http'))
        print('requestUrl:', url)

        # 构造真实请求
        req = QNetworkRequest(url)
        req.setAttribute(self.AttrType, request)  # 记录
        for headerName, headerValue in request.requestHeaders().items():
            req.setRawHeader(headerName, headerValue)
        method = request.requestMethod()

        # TODO: 这里需要把浏览器内部的cookie获取出来重新设置
        if method == b'GET':
            self._manager.get(req)
        # TODO: 这里貌似没法得到POST的数据,ajax的请求貌似也有问题
        elif method == b'POST':
            self._manager.post(req)

    def onFinished(self, reply):
        req = reply.request()  # 获取请求
        o_req = req.attribute(self.AttrType, None)
        if o_req:
            # Notice: 这里可以对数据做修改再返回
            # TODO: 可能还存在 QNetworkAccessManager 与浏览器之间的 cookie 同步问题
            o_req.reply(
                req.header(QNetworkRequest.ContentTypeHeader) or b'text/html',
                reply)
            o_req.destroyed.connect(reply.deleteLater)
예제 #7
0
class Fetch():
    data = QtCore.pyqtSignal(dict)

    def __init__(self, parent):
        self.session = QNetworkAccessManager(parent)
        self.cookies = QNetworkCookieJar()
        self.parent = parent
        self.session.setCookieJar(self.cookies)

    def base_handler(self, reply: QNetworkReply):
        try:
            response = json.loads(str(reply.readAll(), encoding='utf-8'))
            status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        except:
            self.parent.warn.add_warn("Http解析错误")
            return
        if reply.error() != QNetworkReply.NoError:
            self.handler_error(response, status_code)
        else:
            self.data.emit(response)

    def get(self, url, param=None):
        url = QtCore.QUrl(parse_url(url, param))
        request = QNetworkRequest(url)
        reply = self.session.get(request)
        return reply

    def post(self, url, param=None, data=None, json=True):
        if isinstance(data, dict):
            f = ''
            for i in data:
                f += '{}={}&'.format(i, data[i])
            data = f[:-1]
        byte_data = QtCore.QByteArray()
        byte_data.append(data)
        url = QtCore.QUrl(parse_url(url, param))
        request = QNetworkRequest(url)
        if json:
            request.setHeader(QNetworkRequest.ContentTypeHeader, 'application/json')
        reply = self.session.post(request, byte_data)
        return reply

    def handler_error(self, response, status_code):
        if isinstance(response, dict):
            message = response.get('error', 'unknown')
            self.parent.warn.add_warn('网络请求错误,错误码为{},原因为{}'.format(status_code, message))
예제 #8
0
class RequestWidget(QWidget, Requset_Ui):
    """
    Виджет выполнения запроса на сервер
    """
    send = pyqtSignal()
    read_response = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject')
    finish = pyqtSignal('PyQt_PyObject')
    exit = pyqtSignal(bool)
    error = pyqtSignal('PyQt_PyObject')

    def on_error(self, error):
        QMessageBox().critical(self, "Ошибка", str(error))

    def __init__(self, worker: ClientWorker, parent=None, *args, **kwargs):
        QWidget.__init__(self, parent)
        Requset_Ui.setupUi(self, **kwargs)

        self.manager = QNetworkAccessManager(self)

        self.worker = worker

        self.button.clicked.connect(self.send)
        self.send.connect(self.on_run)
        self.manager.finished.connect(self.on_response)
        self.read_response.connect(self.worker.on_response)
        self.worker.finish.connect(self.finish)
        self.progress_bar.finish.connect(self.exit)
        self.progress_bar.finish.connect(self.close)
        self.error.connect(self.on_error)

    @pyqtSlot(QNetworkReply, name='on_response')
    def on_response(self, reply: QNetworkReply):
        self.progress_bar.set_part(10, 1, 'Чтение ответа')
        error_code = reply.error()

        if error_code == QNetworkReply.NoError:
            bytes_string = reply.readAll()

            json_ar = json.loads(str(bytes_string, 'utf-8'))
            print(json_ar)
            if json_ar['status'] == 'OK':
                self.progress_bar.increment()
                self.read_response.emit(json_ar['data'], self.progress_bar)
                self.finish.emit(json_ar['data'])
                self.progress_bar.on_finish('Успешно синхронизированно')
            else:
                self.progress_bar.abord()
                self.button.setEnabled(True)
                self.error.emit(str(error_code))
                self.progress_bar.on_finish(
                    f'Завершено с ошибкой {json_ar["message"]}')
        else:
            print(error_code)
            self.progress_bar.abord()
            self.button.setEnabled(True)
            self.error.emit(error_code)
            self.progress_bar.on_finish(f'Завершено с ошибкой {error_code}')

    @pyqtSlot(name='on_run')
    def on_run(self, *args):
        self.progress_bar.set_part(25, 1, 'Отправка сообщения')

        self.request = QNetworkRequest(QUrl(self.worker.address))
        self.request.setHeader(QNetworkRequest.ContentTypeHeader,
                               "application/json")
        self.manager.post(
            self.request,
            bytearray(JsonParser.dump(self.worker.data), encoding='utf8'))
        self.progress_bar.increment()
        self.button.setEnabled(False)
예제 #9
0
class OctoPrintOutputDevice(NetworkedPrinterOutputDevice):
    def __init__(self,
                 instance_id: str,
                 address: str,
                 port: int,
                 properties: dict,
                 parent=None) -> None:
        super().__init__(device_id=instance_id,
                         address=address,
                         properties=properties,
                         parent=parent)

        self._address = address
        self._port = port
        self._path = properties.get(b"path", b"/").decode("utf-8")
        if self._path[-1:] != "/":
            self._path += "/"
        self._id = instance_id
        self._properties = properties  # Properties dict as provided by zero conf

        self._gcode = []
        self._auto_print = True
        self._forced_queue = False

        # We start with a single extruder, but update this when we get data from octoprint
        self._number_of_extruders_set = False
        self._number_of_extruders = 1

        # Try to get version information from plugin.json
        plugin_file_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "plugin.json")
        try:
            with open(plugin_file_path) as plugin_file:
                plugin_info = json.load(plugin_file)
                plugin_version = plugin_info["version"]
        except:
            # The actual version info is not critical to have so we can continue
            plugin_version = "Unknown"
            Logger.logException(
                "w", "Could not get version information for the plugin")

        self._user_agent_header = "User-Agent".encode()
        self._user_agent = (
            "%s/%s %s/%s" %
            (Application.getInstance().getApplicationName(),
             Application.getInstance().getVersion(), "OctoPrintPlugin",
             Application.getInstance().getVersion())
        )  # NetworkedPrinterOutputDevice defines this as string, so we encode this later

        self._api_prefix = "api/"
        self._api_header = "X-Api-Key".encode()
        self._api_key = None

        self._protocol = "https" if properties.get(
            b'useHttps') == b"true" else "http"
        self._base_url = "%s://%s:%d%s" % (self._protocol, self._address,
                                           self._port, self._path)
        self._api_url = self._base_url + self._api_prefix

        self._basic_auth_header = "Authorization".encode()
        self._basic_auth_data = None
        basic_auth_username = properties.get(b"userName", b"").decode("utf-8")
        basic_auth_password = properties.get(b"password", b"").decode("utf-8")
        if basic_auth_username and basic_auth_password:
            data = base64.b64encode(
                ("%s:%s" % (basic_auth_username,
                            basic_auth_password)).encode()).decode("utf-8")
            self._basic_auth_data = ("basic %s" % data).encode()

        self._monitor_view_qml_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")

        name = self._id
        matches = re.search(r"^\"(.*)\"\._octoprint\._tcp.local$", name)
        if matches:
            name = matches.group(1)

        self.setPriority(
            2
        )  # Make sure the output device gets selected above local file output
        self.setName(name)
        self.setShortDescription(
            i18n_catalog.i18nc("@action:button", "Print with OctoPrint"))
        self.setDescription(
            i18n_catalog.i18nc("@properties:tooltip", "Print with OctoPrint"))
        self.setIconName("print")
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connected to OctoPrint on {0}").format(
                                   self._id))

        #   QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
        #   hook itself into the event loop, which results in events never being fired / done.
        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onRequestFinished)

        ##  Ensure that the qt networking stuff isn't garbage collected (unless we want it to)
        self._settings_reply = None
        self._printer_reply = None
        self._job_reply = None
        self._command_reply = None

        self._post_reply = None
        self._post_multi_part = None

        self._progress_message = None
        self._error_message = None
        self._connection_message = None

        self._queued_gcode_commands = []  # type: List[str]
        self._queued_gcode_timer = QTimer()
        self._queued_gcode_timer.setInterval(0)
        self._queued_gcode_timer.setSingleShot(True)
        self._queued_gcode_timer.timeout.connect(self._sendQueuedGcode)

        self._update_timer = QTimer()
        self._update_timer.setInterval(
            2000)  # TODO; Add preference for update interval
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)

        self._camera_mirror = ""
        self._camera_rotation = 0
        self._camera_url = ""
        self._camera_shares_proxy = False

        self._sd_supported = False

        self._plugin_data = {}  #type: Dict[str, Any]

        self._connection_state_before_timeout = None

        self._last_response_time = None
        self._last_request_time = None
        self._response_timeout_time = 5
        self._recreate_network_manager_time = 30  # If we have no connection, re-create network manager every 30 sec.
        self._recreate_network_manager_count = 1

        self._output_controller = GenericOutputController(self)

    def getProperties(self):
        return self._properties

    @pyqtSlot(str, result=str)
    def getProperty(self, key):
        key = key.encode("utf-8")
        if key in self._properties:
            return self._properties.get(key, b"").decode("utf-8")
        else:
            return ""

    ##  Get the unique key of this machine
    #   \return key String containing the key of the machine.
    @pyqtSlot(result=str)
    def getId(self):
        return self._id

    ##  Set the API key of this OctoPrint instance
    def setApiKey(self, api_key):
        self._api_key = api_key.encode()

    ##  Name of the instance (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def name(self):
        return self._name

    ##  Version (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def octoprintVersion(self):
        return self._properties.get(b"version", b"").decode("utf-8")

    ## IPadress of this instance
    @pyqtProperty(str, constant=True)
    def ipAddress(self):
        return self._address

    ## IPadress of this instance
    #  Overridden from NetworkedPrinterOutputDevice because OctoPrint does not
    #  send the ip address with zeroconf
    @pyqtProperty(str, constant=True)
    def address(self):
        return self._address

    ## port of this instance
    @pyqtProperty(int, constant=True)
    def port(self):
        return self._port

    ## path of this instance
    @pyqtProperty(str, constant=True)
    def path(self):
        return self._path

    ## absolute url of this instance
    @pyqtProperty(str, constant=True)
    def baseURL(self):
        return self._base_url

    cameraOrientationChanged = pyqtSignal()

    @pyqtProperty("QVariantMap", notify=cameraOrientationChanged)
    def cameraOrientation(self):
        return {
            "mirror": self._camera_mirror,
            "rotation": self._camera_rotation,
        }

    def _update(self):
        if self._last_response_time:
            time_since_last_response = time() - self._last_response_time
        else:
            time_since_last_response = 0
        if self._last_request_time:
            time_since_last_request = time() - self._last_request_time
        else:
            time_since_last_request = float(
                "inf")  # An irrelevantly large number of seconds

        # Connection is in timeout, check if we need to re-start the connection.
        # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows.
        # Re-creating the QNetworkManager seems to fix this issue.
        if self._last_response_time and self._connection_state_before_timeout:
            if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count:
                self._recreate_network_manager_count += 1
                # It can happen that we had a very long timeout (multiple times the recreate time).
                # In that case we should jump through the point that the next update won't be right away.
                while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time:
                    self._recreate_network_manager_count += 1
                Logger.log(
                    "d",
                    "Timeout lasted over 30 seconds (%.1fs), re-checking connection.",
                    time_since_last_response)
                self._createNetworkManager()
                return

        # Check if we have an connection in the first place.
        if not self._manager.networkAccessible():
            if not self._connection_state_before_timeout:
                Logger.log(
                    "d",
                    "The network connection seems to be disabled. Going into timeout mode"
                )
                self._connection_state_before_timeout = self._connection_state
                self.setConnectionState(ConnectionState.error)
                self._connection_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "The connection with the network was lost."))
                self._connection_message.show()
                # Check if we were uploading something. Abort if this is the case.
                # Some operating systems handle this themselves, others give weird issues.
                try:
                    if self._post_reply:
                        Logger.log(
                            "d",
                            "Stopping post upload because the connection was lost."
                        )
                        try:
                            self._post_reply.uploadProgress.disconnect(
                                self._onUploadProgress)
                        except TypeError:
                            pass  # The disconnection can fail on mac in some cases. Ignore that.

                        self._post_reply.abort()
                        self._progress_message.hide()
                except RuntimeError:
                    self._post_reply = None  # It can happen that the wrapped c++ object is already deleted.
            return
        else:
            if not self._connection_state_before_timeout:
                self._recreate_network_manager_count = 1

        # Check that we aren't in a timeout state
        if self._last_response_time and self._last_request_time and not self._connection_state_before_timeout:
            if time_since_last_response > self._response_timeout_time and time_since_last_request <= self._response_timeout_time:
                # Go into timeout state.
                Logger.log(
                    "d",
                    "We did not receive a response for %s seconds, so it seems OctoPrint is no longer accesible.",
                    time() - self._last_response_time)
                self._connection_state_before_timeout = self._connection_state
                self._connection_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "The connection with OctoPrint was lost. Check your network-connections."
                    ))
                self._connection_message.show()
                self.setConnectionState(ConnectionState.error)

        ## Request 'general' printer data
        self._printer_reply = self._manager.get(
            self._createApiRequest("printer"))

        ## Request print_job data
        self._job_reply = self._manager.get(self._createApiRequest("job"))

    def _createNetworkManager(self):
        if self._manager:
            self._manager.finished.disconnect(self._onRequestFinished)

        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onRequestFinished)

    def _createApiRequest(self, end_point):
        request = QNetworkRequest(QUrl(self._api_url + end_point))
        request.setRawHeader(self._user_agent_header,
                             self._user_agent.encode())
        request.setRawHeader(self._api_header, self._api_key)
        if self._basic_auth_data:
            request.setRawHeader(self._basic_auth_header,
                                 self._basic_auth_data)
        return request

    def close(self):
        self.setConnectionState(ConnectionState.closed)
        if self._progress_message:
            self._progress_message.hide()
        if self._error_message:
            self._error_message.hide()
        self._update_timer.stop()

    def requestWrite(self,
                     node,
                     file_name=None,
                     filter_by_machine=False,
                     file_handler=None,
                     **kwargs):
        self.writeStarted.emit(self)

        active_build_plate = Application.getInstance().getMultiBuildPlateModel(
        ).activeBuildPlate
        scene = Application.getInstance().getController().getScene()
        gcode_dict = getattr(scene, "gcode_dict", None)
        if not gcode_dict:
            return
        self._gcode = gcode_dict.get(active_build_plate, None)

        self.startPrint()

    ##  Start requesting data from the instance
    def connect(self):
        self._createNetworkManager()

        self.setConnectionState(ConnectionState.connecting)
        self._update(
        )  # Manually trigger the first update, as we don't want to wait a few secs before it starts.
        Logger.log("d", "Connection with instance %s with url %s started",
                   self._id, self._base_url)
        self._update_timer.start()

        self._last_response_time = None
        self._setAcceptsCommands(False)
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connecting to OctoPrint on {0}").format(
                                   self._id))

        ## Request 'settings' dump
        self._settings_reply = self._manager.get(
            self._createApiRequest("settings"))

    ##  Stop requesting data from the instance
    def disconnect(self):
        Logger.log("d", "Connection with instance %s with url %s stopped",
                   self._id, self._base_url)
        self.close()

    def pausePrint(self):
        self._sendJobCommand("pause")

    def resumePrint(self):
        if not self._printers[0].activePrintJob:
            return

        if self._printers[0].activePrintJob.state == "paused":
            self._sendJobCommand("pause")
        else:
            self._sendJobCommand("start")

    def cancelPrint(self):
        self._sendJobCommand("cancel")

    def startPrint(self):
        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        if not global_container_stack:
            return

        if self._error_message:
            self._error_message.hide()
            self._error_message = None

        if self._progress_message:
            self._progress_message.hide()
            self._progress_message = None

        self._auto_print = parseBool(
            global_container_stack.getMetaDataEntry("octoprint_auto_print",
                                                    True))
        self._forced_queue = False

        if self.activePrinter.state not in ["idle", ""]:
            Logger.log(
                "d", "Tried starting a print, but current state is %s" %
                self.activePrinter.state)
            if not self._auto_print:
                # allow queueing the job even if OctoPrint is currently busy if autoprinting is disabled
                self._error_message = None
            elif self.activePrinter.state == "offline":
                self._error_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "The printer is offline. Unable to start a new job."))
            else:
                self._error_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "OctoPrint is busy. Unable to start a new job."))

            if self._error_message:
                self._error_message.addAction(
                    "Queue", i18n_catalog.i18nc("@action:button", "Queue job"),
                    None,
                    i18n_catalog.i18nc(
                        "@action:tooltip",
                        "Queue this print job so it can be printed later"))
                self._error_message.actionTriggered.connect(self._queuePrint)
                self._error_message.show()
                return

        self._startPrint()

    def _queuePrint(self, message_id, action_id):
        if self._error_message:
            self._error_message.hide()
        self._forced_queue = True
        self._startPrint()

    def _startPrint(self):
        if self._auto_print and not self._forced_queue:
            Application.getInstance().getController().setActiveStage(
                "MonitorStage")

            # cancel any ongoing preheat timer before starting a print
            try:
                self._printers[0].stopPreheatTimers()
            except AttributeError:
                # stopPreheatTimers was added after Cura 3.3 beta
                pass

        self._progress_message = Message(
            i18n_catalog.i18nc("@info:status", "Sending data to OctoPrint"), 0,
            False, -1)
        self._progress_message.addAction(
            "Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
        self._progress_message.actionTriggered.connect(self._cancelSendGcode)
        self._progress_message.show()

        ## Mash the data into single string
        single_string_file_data = ""
        last_process_events = time()
        for line in self._gcode:
            single_string_file_data += line
            if time() > last_process_events + 0.05:
                # Ensure that the GUI keeps updated at least 20 times per second.
                QCoreApplication.processEvents()
                last_process_events = time()

        job_name = Application.getInstance().getPrintInformation(
        ).jobName.strip()
        if job_name is "":
            job_name = "untitled_print"
        file_name = "%s.gcode" % job_name

        ##  Create multi_part request
        self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)

        ##  Create parts (to be placed inside multipart)
        post_part = QHttpPart()
        post_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                            "form-data; name=\"select\"")
        post_part.setBody(b"true")
        self._post_multi_part.append(post_part)

        if self._auto_print and not self._forced_queue:
            post_part = QHttpPart()
            post_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                                "form-data; name=\"print\"")
            post_part.setBody(b"true")
            self._post_multi_part.append(post_part)

        post_part = QHttpPart()
        post_part.setHeader(
            QNetworkRequest.ContentDispositionHeader,
            "form-data; name=\"file\"; filename=\"%s\"" % file_name)
        post_part.setBody(single_string_file_data.encode())
        self._post_multi_part.append(post_part)

        destination = "local"
        if self._sd_supported and parseBool(Application.getInstance(
        ).getGlobalContainerStack().getMetaDataEntry("octoprint_store_sd",
                                                     False)):
            destination = "sdcard"

        try:
            ##  Post request + data
            post_request = self._createApiRequest("files/" + destination)
            self._post_reply = self._manager.post(post_request,
                                                  self._post_multi_part)
            self._post_reply.uploadProgress.connect(self._onUploadProgress)

        except IOError:
            self._progress_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Unable to send data to OctoPrint."))
            self._error_message.show()
        except Exception as e:
            self._progress_message.hide()
            Logger.log(
                "e",
                "An exception occurred in network connection: %s" % str(e))

        self._gcode = []

    def _cancelSendGcode(self, message_id, action_id):
        if self._post_reply:
            Logger.log("d", "Stopping upload because the user pressed cancel.")
            try:
                self._post_reply.uploadProgress.disconnect(
                    self._onUploadProgress)
            except TypeError:
                pass  # The disconnection can fail on mac in some cases. Ignore that.

            self._post_reply.abort()
            self._post_reply = None
        if self._progress_message:
            self._progress_message.hide()

    def sendCommand(self, command):
        self._queued_gcode_commands.append(command)
        self._queued_gcode_timer.start()

    # Send gcode commands that are queued in quick succession as a single batch
    def _sendQueuedGcode(self):
        if self._queued_gcode_commands:
            self._sendCommandToApi("printer/command",
                                   self._queued_gcode_commands)
            Logger.log("d", "Sent gcode command to OctoPrint instance: %s",
                       self._queued_gcode_commands)
            self._queued_gcode_commands = []

    def _sendJobCommand(self, command):
        self._sendCommandToApi("job", command)
        Logger.log("d", "Sent job command to OctoPrint instance: %s", command)

    def _sendCommandToApi(self, end_point, commands):
        command_request = self._createApiRequest(end_point)
        command_request.setHeader(QNetworkRequest.ContentTypeHeader,
                                  "application/json")

        if isinstance(commands, list):
            data = json.dumps({"commands": commands})
        else:
            data = json.dumps({"command": commands})
        self._command_reply = self._manager.post(command_request,
                                                 data.encode())

    ##  Handler for all requests that have finished.
    def _onRequestFinished(self, reply):
        if reply.error() == QNetworkReply.TimeoutError:
            Logger.log("w", "Received a timeout on a request to the instance")
            self._connection_state_before_timeout = self._connection_state
            self.setConnectionState(ConnectionState.error)
            return

        if self._connection_state_before_timeout and reply.error(
        ) == QNetworkReply.NoError:  #  There was a timeout, but we got a correct answer again.
            if self._last_response_time:
                Logger.log(
                    "d",
                    "We got a response from the instance after %s of silence",
                    time() - self._last_response_time)
            self.setConnectionState(self._connection_state_before_timeout)
            self._connection_state_before_timeout = None

        if reply.error() == QNetworkReply.NoError:
            self._last_response_time = time()

        http_status_code = reply.attribute(
            QNetworkRequest.HttpStatusCodeAttribute)
        if not http_status_code:
            # Received no or empty reply
            return

        error_handled = False

        if reply.operation() == QNetworkAccessManager.GetOperation:
            if self._api_prefix + "printer" in reply.url().toString(
            ):  # Status update from /printer.
                if not self._printers:
                    self._createPrinterList()

                # An OctoPrint instance has a single printer.
                printer = self._printers[0]

                if http_status_code == 200:
                    if not self.acceptsCommands:
                        self._setAcceptsCommands(True)
                        self.setConnectionText(
                            i18n_catalog.i18nc(
                                "@info:status",
                                "Connected to OctoPrint on {0}").format(
                                    self._id))

                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if "temperature" in json_data:
                        if not self._number_of_extruders_set:
                            self._number_of_extruders = 0
                            while "tool%d" % self._number_of_extruders in json_data[
                                    "temperature"]:
                                self._number_of_extruders += 1

                            if self._number_of_extruders > 1:
                                # Recreate list of printers to match the new _number_of_extruders
                                self._createPrinterList()
                                printer = self._printers[0]

                            if self._number_of_extruders > 0:
                                self._number_of_extruders_set = True

                        # Check for hotend temperatures
                        for index in range(0, self._number_of_extruders):
                            extruder = printer.extruders[index]
                            if ("tool%d" % index) in json_data["temperature"]:
                                hotend_temperatures = json_data["temperature"][
                                    "tool%d" % index]
                                extruder.updateTargetHotendTemperature(
                                    hotend_temperatures["target"])
                                extruder.updateHotendTemperature(
                                    hotend_temperatures["actual"])
                            else:
                                extruder.updateTargetHotendTemperature(0)
                                extruder.updateHotendTemperature(0)

                        if "bed" in json_data["temperature"]:
                            bed_temperatures = json_data["temperature"]["bed"]
                            actual_temperature = bed_temperatures[
                                "actual"] if bed_temperatures[
                                    "actual"] is not None else -1
                            printer.updateBedTemperature(actual_temperature)
                            target_temperature = bed_temperatures[
                                "target"] if bed_temperatures[
                                    "target"] is not None else -1
                            printer.updateTargetBedTemperature(
                                target_temperature)
                        else:
                            printer.updateBedTemperature(-1)
                            printer.updateTargetBedTemperature(0)

                    printer_state = "offline"
                    if "state" in json_data:
                        flags = json_data["state"]["flags"]
                        if flags["error"] or flags["closedOrError"]:
                            printer_state = "error"
                        elif flags["paused"] or flags["pausing"]:
                            printer_state = "paused"
                        elif flags["printing"]:
                            printer_state = "printing"
                        elif flags["cancelling"]:
                            printer_state = "aborted"
                        elif flags["ready"] or flags["operational"]:
                            printer_state = "idle"
                    printer.updateState(printer_state)

                elif http_status_code == 401:
                    printer.updateState("offline")
                    if printer.activePrintJob:
                        printer.activePrintJob.updateState("offline")
                    self.setConnectionText(
                        i18n_catalog.i18nc(
                            "@info:status",
                            "OctoPrint on {0} does not allow access to print").
                        format(self._id))
                    error_handled = True

                elif http_status_code == 409:
                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)

                    printer.updateState("offline")
                    if printer.activePrintJob:
                        printer.activePrintJob.updateState("offline")
                    self.setConnectionText(
                        i18n_catalog.i18nc(
                            "@info:status",
                            "The printer connected to OctoPrint on {0} is not operational"
                        ).format(self._id))
                    error_handled = True
                else:
                    printer.updateState("offline")
                    if printer.activePrintJob:
                        printer.activePrintJob.updateState("offline")
                    Logger.log("w", "Received an unexpected returncode: %d",
                               http_status_code)

            elif self._api_prefix + "job" in reply.url().toString(
            ):  # Status update from /job:
                if not self._printers:
                    return  # Ignore the data for now, we don't have info about a printer yet.
                printer = self._printers[0]

                if http_status_code == 200:
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if printer.activePrintJob is None:
                        print_job = PrintJobOutputModel(
                            output_controller=self._output_controller)
                        printer.updateActivePrintJob(print_job)
                    else:
                        print_job = printer.activePrintJob

                    print_job_state = "offline"
                    if "state" in json_data:
                        if json_data["state"] == "Error":
                            print_job_state = "error"
                        elif json_data["state"] == "Pausing":
                            print_job_state = "pausing"
                        elif json_data["state"] == "Paused":
                            print_job_state = "paused"
                        elif json_data["state"] == "Printing":
                            print_job_state = "printing"
                        elif json_data["state"] == "Cancelling":
                            print_job_state = "abort"
                        elif json_data["state"] == "Operational":
                            print_job_state = "ready"
                            printer.updateState("idle")
                    print_job.updateState(print_job_state)

                    print_time = json_data["progress"]["printTime"]
                    if print_time:
                        print_job.updateTimeElapsed(print_time)
                        if json_data["progress"][
                                "completion"]:  # not 0 or None or ""
                            print_job.updateTimeTotal(
                                print_time /
                                (json_data["progress"]["completion"] / 100))
                        else:
                            print_job.updateTimeTotal(0)
                    else:
                        print_job.updateTimeElapsed(0)
                        print_job.updateTimeTotal(0)

                    print_job.updateName(json_data["job"]["file"]["name"])
                else:
                    pass  # See generic error handler below

            elif self._api_prefix + "settings" in reply.url().toString(
            ):  # OctoPrint settings dump from /settings:
                if http_status_code == 200:
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if "feature" in json_data and "sdSupport" in json_data[
                            "feature"]:
                        self._sd_supported = json_data["feature"]["sdSupport"]

                    if "webcam" in json_data and "streamUrl" in json_data[
                            "webcam"]:
                        self._camera_shares_proxy = False
                        stream_url = json_data["webcam"]["streamUrl"]
                        if not stream_url:  #empty string or None
                            self._camera_url = ""
                        elif stream_url[:4].lower() == "http":  # absolute uri
                            self._camera_url = stream_url
                        elif stream_url[:2] == "//":  # protocol-relative
                            self._camera_url = "%s:%s" % (self._protocol,
                                                          stream_url)
                        elif stream_url[:
                                        1] == ":":  # domain-relative (on another port)
                            self._camera_url = "%s://%s%s" % (
                                self._protocol, self._address, stream_url)
                        elif stream_url[:
                                        1] == "/":  # domain-relative (on same port)
                            self._camera_url = "%s://%s:%d%s" % (
                                self._protocol, self._address, self._port,
                                stream_url)
                            self._camera_shares_proxy = True
                        else:
                            Logger.log("w", "Unusable stream url received: %s",
                                       stream_url)
                            self._camera_url = ""

                        Logger.log("d", "Set OctoPrint camera url to %s",
                                   self._camera_url)
                        if self._camera_url != "" and len(self._printers) > 0:
                            self._printers[0].setCamera(
                                NetworkCamera(self._camera_url))

                        if "rotate90" in json_data["webcam"]:
                            self._camera_rotation = -90 if json_data["webcam"][
                                "rotate90"] else 0
                            if json_data["webcam"]["flipH"] and json_data[
                                    "webcam"]["flipV"]:
                                self._camera_mirror = False
                                self._camera_rotation += 180
                            elif json_data["webcam"]["flipH"]:
                                self._camera_mirror = True
                            elif json_data["webcam"]["flipV"]:
                                self._camera_mirror = True
                                self._camera_rotation += 180
                            else:
                                self._camera_mirror = False
                            self.cameraOrientationChanged.emit()

                    if "plugins" in json_data:
                        self._plugin_data = json_data["plugins"]

                        can_update_firmware = "firmwareupdater" in self._plugin_data
                        self._output_controller.setCanUpdateFirmware(
                            can_update_firmware)

        elif reply.operation() == QNetworkAccessManager.PostOperation:
            if self._api_prefix + "files" in reply.url().toString(
            ):  # Result from /files command:
                if http_status_code == 201:
                    Logger.log(
                        "d", "Resource created on OctoPrint instance: %s",
                        reply.header(
                            QNetworkRequest.LocationHeader).toString())
                else:
                    pass  # See generic error handler below

                reply.uploadProgress.disconnect(self._onUploadProgress)
                self._progress_message.hide()

                if self._forced_queue or not self._auto_print:
                    location = reply.header(QNetworkRequest.LocationHeader)
                    if location:
                        file_name = QUrl(
                            reply.header(QNetworkRequest.LocationHeader).
                            toString()).fileName()
                        message = Message(
                            i18n_catalog.i18nc(
                                "@info:status",
                                "Saved to OctoPrint as {0}").format(file_name))
                    else:
                        message = Message(
                            i18n_catalog.i18nc("@info:status",
                                               "Saved to OctoPrint"))
                    message.addAction(
                        "open_browser",
                        i18n_catalog.i18nc("@action:button", "OctoPrint..."),
                        "globe",
                        i18n_catalog.i18nc("@info:tooltip",
                                           "Open the OctoPrint web interface"))
                    message.actionTriggered.connect(
                        self._onMessageActionTriggered)
                    message.show()

            elif self._api_prefix + "job" in reply.url().toString(
            ):  # Result from /job command (eg start/pause):
                if http_status_code == 204:
                    Logger.log("d", "Octoprint job command accepted")
                else:
                    pass  # See generic error handler below

            elif self._api_prefix + "printer/command" in reply.url().toString(
            ):  # Result from /printer/command (gcode statements):
                if http_status_code == 204:
                    Logger.log("d", "Octoprint gcode command(s) accepted")
                else:
                    pass  # See generic error handler below

        else:
            Logger.log("d",
                       "OctoPrintOutputDevice got an unhandled operation %s",
                       reply.operation())

        if not error_handled and http_status_code >= 400:
            # Received an error reply
            error_string = reply.attribute(
                QNetworkRequest.HttpReasonPhraseAttribute).decode("utf-8")
            if self._error_message:
                self._error_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "OctoPrint returned an error: {0}.").format(error_string))
            self._error_message.show()
            return

    def _onUploadProgress(self, bytes_sent, bytes_total):
        if bytes_total > 0:
            # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
            # timeout responses if this happens.
            self._last_response_time = time()

            progress = bytes_sent / bytes_total * 100
            if progress < 100:
                if progress > self._progress_message.getProgress():
                    self._progress_message.setProgress(progress)
            else:
                self._progress_message.hide()
                self._progress_message = Message(
                    i18n_catalog.i18nc("@info:status",
                                       "Storing data on OctoPrint"), 0, False,
                    -1)
                self._progress_message.show()
        else:
            self._progress_message.setProgress(0)

    def _createPrinterList(self):
        printer = PrinterOutputModel(
            output_controller=self._output_controller,
            number_of_extruders=self._number_of_extruders)
        if self._camera_url != "":
            printer.setCamera(NetworkCamera(self._camera_url))
        printer.updateName(self.name)
        self._printers = [printer]
        self.printersChanged.emit()

    def _onMessageActionTriggered(self, message, action):
        if action == "open_browser":
            QDesktopServices.openUrl(QUrl(self._base_url))
class DiscoverRepetierAction(MachineAction):
    def __init__(self, parent: QObject = None) -> None:
        super().__init__("DiscoverRepetierAction",
                         catalog.i18nc("@action", "Connect Repetier"))

        self._qml_url = "DiscoverRepetierAction.qml"

        self._application = CuraApplication.getInstance()
        self._network_plugin = None

        #   QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
        #   hook itself into the event loop, which results in events never being fired / done.
        self._network_manager = QNetworkAccessManager()
        self._network_manager.finished.connect(self._onRequestFinished)
        self._printers = [""]
        self._printerlist_reply = None
        self._settings_reply = None
        self._settings_reply_timeout = None  # type: Optional[NetworkReplyTimeout]

        self._instance_supports_appkeys = False
        self._appkey_reply = None  # type: Optional[QNetworkReply]
        self._appkey_request = None  # type: Optional[QNetworkRequest]
        self._appkey_instance_id = ""

        self._appkey_poll_timer = QTimer()
        self._appkey_poll_timer.setInterval(500)
        self._appkey_poll_timer.setSingleShot(True)
        self._appkey_poll_timer.timeout.connect(self._pollApiKey)

        # Try to get version information from plugin.json
        plugin_file_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "plugin.json")
        try:
            with open(plugin_file_path) as plugin_file:
                plugin_info = json.load(plugin_file)
                self._plugin_version = plugin_info["version"]
        except:
            # The actual version info is not critical to have so we can continue
            self._plugin_version = "0.0"
            Logger.logException(
                "w", "Could not get version information for the plugin")

        self._user_agent = (
            "%s/%s %s/%s" %
            (self._application.getApplicationName(),
             self._application.getVersion(), "RepetierIntegration",
             self._plugin_version)).encode()

        self._settings_instance = None

        self._instance_responded = False
        self._instance_in_error = False
        self._instance_api_key_accepted = False
        self._instance_supports_sd = False
        self._instance_supports_camera = False
        self._instance_webcamflip_y = False
        self._instance_webcamflip_x = False
        self._instance_webcamrot90 = False
        self._instance_webcamrot270 = False

        # Load keys cache from preferences
        self._preferences = self._application.getPreferences()
        self._preferences.addPreference("Repetier/keys_cache", "")

        try:
            self._keys_cache = json.loads(
                self._preferences.getValue("Repetier/keys_cache"))
        except ValueError:
            self._keys_cache = {}
        if not isinstance(self._keys_cache, dict):
            self._keys_cache = {}

        self._additional_components = None

        ContainerRegistry.getInstance().containerAdded.connect(
            self._onContainerAdded)
        self._application.engineCreatedSignal.connect(
            self._createAdditionalComponentsView)

    @pyqtProperty(str, constant=True)
    def pluginVersion(self) -> str:
        return self._plugin_version

    @pyqtSlot()
    def startDiscovery(self) -> None:
        if not self._plugin_id:
            return
        if not self._network_plugin:
            self._network_plugin = cast(
                RepetierOutputDevicePlugin,
                self._application.getOutputDeviceManager().
                getOutputDevicePlugin(self._plugin_id))
            if not self._network_plugin:
                return
            self._network_plugin.addInstanceSignal.connect(
                self._onInstanceDiscovery)
            self._network_plugin.removeInstanceSignal.connect(
                self._onInstanceDiscovery)
            self._network_plugin.instanceListChanged.connect(
                self._onInstanceDiscovery)
            self.instancesChanged.emit()
        else:
            # Restart bonjour discovery
            self._network_plugin.startDiscovery()

    def _onInstanceDiscovery(self, *args) -> None:
        self.instancesChanged.emit()

    @pyqtSlot(str)
    def removeManualInstance(self, name: str) -> None:
        if not self._network_plugin:
            return

        self._network_plugin.removeManualInstance(name)

    @pyqtSlot(str, str, int, str, bool, str, str, str)
    def setManualInstance(self, name, address, port, path, useHttps, userName,
                          password, repetierid):
        if not self._network_plugin:
            return
        # This manual printer could replace a current manual printer
        self._network_plugin.removeManualInstance(name)

        self._network_plugin.addManualInstance(name, address, port, path,
                                               useHttps, userName, password,
                                               repetierid)

    def _onContainerAdded(self, container: "ContainerInterface") -> None:
        # Add this action as a supported action to all machine definitions
        if (isinstance(container, DefinitionContainer)
                and container.getMetaDataEntry("type") == "machine"
                and container.getMetaDataEntry("supports_usb_connection")):

            self._application.getMachineActionManager().addSupportedAction(
                container.getId(), self.getKey())

    instancesChanged = pyqtSignal()
    appKeysSupportedChanged = pyqtSignal()
    appKeyReceived = pyqtSignal()
    instanceIdChanged = pyqtSignal()

    @pyqtProperty("QVariantList", notify=instancesChanged)
    def discoveredInstances(self) -> List[Any]:
        if self._network_plugin:
            instances = list(self._network_plugin.getInstances().values())
            instances.sort(key=lambda k: k.name)
            return instances
        else:
            return []

    @pyqtSlot(str)
    def setInstanceId(self, key: str) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if global_container_stack:
            global_container_stack.setMetaDataEntry("repetier_id", key)

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

        self.instanceIdChanged.emit()

    @pyqtProperty(str, notify=instanceIdChanged)
    def instanceId(self) -> str:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return ""

        return global_container_stack.getMetaDataEntry("repetier_id", "")

    @pyqtSlot(str)
    @pyqtSlot(result=str)
    def getInstanceId(self) -> str:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            Logger.log(
                "d",
                "getInstancdId - self._application.getGlobalContainerStack() returned nothing"
            )
            return ""

        return global_container_stack.getMetaDataEntry("repetier_id", "")

    @pyqtSlot(str)
    def requestApiKey(self, instance_id: str) -> None:
        (instance, base_url, basic_auth_username,
         basic_auth_password) = self._getInstanceInfo(instance_id)

        if not base_url:

            return

        ## Request appkey
        self._appkey_instance_id = instance_id
        self._appkey_request = self._createRequest(
            QUrl(base_url + "plugin/appkeys/request"), basic_auth_username,
            basic_auth_password)
        self._appkey_request.setRawHeader(b"Content-Type", b"application/json")
        data = json.dumps({"app": "Cura"})
        self._appkey_reply = self._network_manager.post(
            self._appkey_request, data.encode())

    @pyqtSlot()
    def cancelApiKeyRequest(self) -> None:
        if self._appkey_reply:
            if self._appkey_reply.isRunning():
                self._appkey_reply.abort()
            self._appkey_reply = None

        self._appkey_request = None  # type: Optional[QNetworkRequest]

        self._appkey_poll_timer.stop()

    def _pollApiKey(self) -> None:
        if not self._appkey_request:
            return
        self._appkey_reply = self._network_manager.get(self._appkey_request)

    @pyqtSlot(str)
    def probeAppKeySupport(self, instance_id: str) -> None:
        (instance, base_url, basic_auth_username,
         basic_auth_password) = self._getInstanceInfo(instance_id)
        if not base_url or not instance:
            return

        instance.getAdditionalData()

        self._instance_supports_appkeys = False
        self.appKeysSupportedChanged.emit()

        appkey_probe_request = self._createRequest(
            QUrl(base_url + "plugin/appkeys/probe"), basic_auth_username,
            basic_auth_password)
        self._appkey_reply = self._network_manager.get(appkey_probe_request)

    @pyqtSlot(str)
    def getPrinterList(self, base_url):
        self._instance_responded = False
        url = QUrl("http://" + base_url + "/printer/info")
        Logger.log("d", "getPrinterList:" + url.toString())
        settings_request = QNetworkRequest(url)
        settings_request.setRawHeader("User-Agent".encode(), self._user_agent)
        self._printerlist_reply = self._network_manager.get(settings_request)
        return self._printers

    @pyqtSlot(str, str, str, str, str, str)
    def testApiKey(self,
                   instance_id: str,
                   base_url,
                   api_key,
                   basic_auth_username="",
                   basic_auth_password="",
                   work_id="") -> None:
        (instance, base_url, basic_auth_username,
         basic_auth_password) = self._getInstanceInfo(instance_id)
        self._instance_responded = False
        self._instance_api_key_accepted = False
        self._instance_supports_sd = False
        self._instance_webcamflip_y = False
        self._instance_webcamflip_x = False
        self._instance_webcamrot90 = False
        self._instance_webcamrot270 = False
        self._instance_supports_camera = False
        self.selectedInstanceSettingsChanged.emit()
        if self._settings_reply:
            if self._settings_reply.isRunning():
                self._settings_reply.abort()
            self._settings_reply = None
        if self._settings_reply_timeout:
            self._settings_reply_timeout = None
        if ((api_key != "") and (api_key != None) and (work_id != "")):
            Logger.log(
                "d",
                "Trying to access Repetier instance at %s with the provided API key."
                % base_url)
            Logger.log("d", "Using %s as work_id" % work_id)
            Logger.log("d", "Using %s as api_key" % api_key)
            url = QUrl(base_url + "/printer/api/" + work_id +
                       "?a=getPrinterConfig&apikey=" + api_key)
            settings_request = QNetworkRequest(url)
            settings_request.setRawHeader("x-api-key".encode(),
                                          api_key.encode())
            settings_request.setRawHeader("User-Agent".encode(),
                                          self._user_agent)
            if basic_auth_username and basic_auth_password:
                data = base64.b64encode(
                    ("%s:%s" % (basic_auth_username,
                                basic_auth_password)).encode()).decode("utf-8")
                settings_request.setRawHeader("Authorization".encode(),
                                              ("Basic %s" % data).encode())
            self._settings_reply = self._network_manager.get(settings_request)
            self._settings_instance = instance
        else:
            self.getPrinterList(base_url)

    @pyqtSlot(str)
    def setApiKey(self, api_key: str) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return
        global_container_stack.setMetaDataEntry("repetier_api_key", api_key)
        self._keys_cache[self.getInstanceId()] = api_key
        keys_cache = base64.b64encode(
            json.dumps(self._keys_cache).encode("ascii")).decode("ascii")
        self._preferences.setValue("Repetier/keys_cache", keys_cache)

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

    #  Get the stored API key of this machine
    #   \return key String containing the key of the machine.
    @pyqtSlot(str, result=str)
    def getApiKey(self, instance_id: str) -> str:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return ""
        Logger.log(
            "d", "APIKEY read %s" %
            global_container_stack.getMetaDataEntry("repetier_api_key", ""))
        if instance_id == self.getInstanceId():
            api_key = global_container_stack.getMetaDataEntry(
                "repetier_api_key", "")
        else:
            api_key = self._keys_cache.get(instance_id, "")
        return api_key

    selectedInstanceSettingsChanged = pyqtSignal()

    @pyqtProperty(list)
    def getPrinters(self):
        return self._printers

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceResponded(self) -> bool:
        return self._instance_responded

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceInError(self) -> bool:
        return self._instance_in_error

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceApiKeyAccepted(self) -> bool:
        return self._instance_api_key_accepted

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceSupportsSd(self) -> bool:
        return self._instance_supports_sd

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceWebcamFlipY(self):
        return self._instance_webcamflip_y

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceWebcamFlipX(self):
        return self._instance_webcamflip_x

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceWebcamRot90(self):
        return self._instance_webcamrot90

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceWebcamRot270(self):
        return self._instance_webcamrot270

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceSupportsCamera(self) -> bool:
        return self._instance_supports_camera

    @pyqtSlot(str, str, str)
    def setContainerMetaDataEntry(self, container_id: str, key: str,
                                  value: str) -> None:
        containers = ContainerRegistry.getInstance().findContainers(
            id=container_id)
        if not containers:
            Logger.log(
                "w",
                "Could not set metadata of container %s because it was not found.",
                container_id)
            return

        containers[0].setMetaDataEntry(key, value)

    @pyqtSlot(bool)
    def applyGcodeFlavorFix(self, apply_fix: bool) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return

        gcode_flavor = "RepRap (Marlin/Sprinter)" if apply_fix else "UltiGCode"
        if global_container_stack.getProperty("machine_gcode_flavor",
                                              "value") == gcode_flavor:
            # No need to add a definition_changes container if the setting is not going to be changed
            return

        # Make sure there is a definition_changes container to store the machine settings
        definition_changes_container = global_container_stack.definitionChanges
        if definition_changes_container == ContainerRegistry.getInstance(
        ).getEmptyInstanceContainer():
            definition_changes_container = CuraStackBuilder.createDefinitionChangesContainer(
                global_container_stack,
                global_container_stack.getId() + "_settings")

        definition_changes_container.setProperty("machine_gcode_flavor",
                                                 "value", gcode_flavor)

        # Update the has_materials metadata flag after switching gcode flavor
        definition = global_container_stack.getBottom()
        if (not definition or definition.getProperty("machine_gcode_flavor",
                                                     "value") != "UltiGCode"
                or definition.getMetaDataEntry("has_materials", False)):

            # In other words: only continue for the UM2 (extended), but not for the UM2+
            return

        has_materials = global_container_stack.getProperty(
            "machine_gcode_flavor", "value") != "UltiGCode"

        material_container = global_container_stack.material

        if has_materials:
            global_container_stack.setMetaDataEntry("has_materials", True)

            # Set the material container to a sane default
            if material_container == ContainerRegistry.getInstance(
            ).getEmptyInstanceContainer():
                search_criteria = {
                    "type":
                    "material",
                    "definition":
                    "fdmprinter",
                    "id":
                    global_container_stack.getMetaDataEntry(
                        "preferred_material")
                }
                materials = ContainerRegistry.getInstance(
                ).findInstanceContainers(**search_criteria)
                if materials:
                    global_container_stack.material = materials[0]
        else:
            # The metadata entry is stored in an ini, and ini files are parsed as strings only.
            # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
            if "has_materials" in global_container_stack.getMetaData():
                global_container_stack.removeMetaDataEntry("has_materials")

            global_container_stack.material = ContainerRegistry.getInstance(
            ).getEmptyInstanceContainer()

        self._application.globalContainerStackChanged.emit()

    @pyqtSlot(str)
    def openWebPage(self, url: str) -> None:
        QDesktopServices.openUrl(QUrl(url))

    def _createAdditionalComponentsView(self) -> None:
        Logger.log(
            "d",
            "Creating additional ui components for Repetier-connected printers."
        )

        path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                            "RepetierComponents.qml")
        self._additional_components = self._application.createQmlComponent(
            path, {"manager": self})
        if not self._additional_components:
            Logger.log(
                "w",
                "Could not create additional components for Repetier-connected printers."
            )
            return

        self._application.addAdditionalComponent(
            "monitorButtons",
            self._additional_components.findChild(QObject,
                                                  "openRepetierButton"))

    def _onRequestFailed(self, reply: QNetworkReply) -> None:
        if reply.operation() == QNetworkAccessManager.GetOperation:
            if "api/settings" in reply.url().toString(
            ):  # Repetier settings dump from /settings:
                Logger.log(
                    "w",
                    "Connection refused or timeout when trying to access Repetier at %s"
                    % reply.url().toString())
                self._instance_in_error = True
                self.selectedInstanceSettingsChanged.emit()

    #  Handler for all requests that have finished.
    def _onRequestFinished(self, reply: QNetworkReply) -> None:
        if reply.error() == QNetworkReply.TimeoutError:
            QMessageBox.warning(None, 'Connection Timeout',
                                'Connection Timeout')
            return
        http_status_code = reply.attribute(
            QNetworkRequest.HttpStatusCodeAttribute)
        if not http_status_code:
            #QMessageBox.warning(None,'Connection Attempt2',http_status_code)
            # Received no or empty reply
            Logger.log("d", "Received no or empty reply")
            return

        if reply.operation() == QNetworkAccessManager.GetOperation:
            Logger.log("d", reply.url().toString())
            if "printer/info" in reply.url().toString(
            ):  # Repetier settings dump from printer/info:
                if http_status_code == 200:
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                        Logger.log("d", reply.url().toString())
                        Logger.log("d", json_data)
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from Repetier instance.")
                        json_data = {}

                    if "printers" in json_data:
                        Logger.log("d", "DiscoverRepetierAction: printers: %s",
                                   len(json_data["printers"]))
                        if len(json_data["printers"]) > 0:
                            self._printers = [""]
                            for printerinfo in json_data["printers"]:
                                Logger.log("d", "Slug: %s",
                                           printerinfo["slug"])
                                self._printers.append(printerinfo["slug"])

                    if "apikey" in json_data:
                        Logger.log("d", "DiscoverRepetierAction: apikey: %s",
                                   json_data["apikey"])
                        global_container_stack = self._application.getGlobalContainerStack(
                        )
                        if not global_container_stack:
                            return
                        global_container_stack.setMetaDataEntry(
                            "repetier_api_key", json_data["apikey"])
                        self._keys_cache[
                            self.getInstanceId()] = json_data["apikey"]
                        keys_cache = base64.b64encode(
                            json.dumps(self._keys_cache).encode(
                                "ascii")).decode("ascii")
                        self._preferences.setValue("Repetier/keys_cache",
                                                   keys_cache)
                        self.appKeyReceived.emit()

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

            if "getPrinterConfig" in reply.url().toString(
            ):  # Repetier settings dump from getPrinterConfig:
                if http_status_code == 200:
                    Logger.log("d", "API key accepted by Repetier.")
                    self._instance_api_key_accepted = True

                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                        Logger.log("d", reply.url().toString())
                        Logger.log("d", json_data)
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from Repetier instance.")
                        json_data = {}

                    if "general" in json_data and "sdcard" in json_data[
                            "general"]:
                        self._instance_supports_sd = json_data["general"][
                            "sdcard"]

                    if "webcam" in json_data and "dynamicUrl" in json_data[
                            "webcam"]:
                        Logger.log(
                            "d", "DiscoverRepetierAction: Checking streamurl")
                        Logger.log("d", "DiscoverRepetierAction: %s",
                                   reply.url())
                        stream_url = json_data["webcam"]["dynamicUrl"].replace(
                            "127.0.0.1",
                            re.findall(r'[0-9]+(?:\.[0-9]+){3}',
                                       reply.url().toString())[0])
                        Logger.log("d",
                                   "DiscoverRepetierAction: stream_url: %s",
                                   stream_url)
                        Logger.log("d",
                                   "DiscoverRepetierAction: reply_url: %s",
                                   reply.url())
                        if stream_url:  #not empty string or None
                            self._instance_supports_camera = True
                    if "webcams" in json_data:
                        Logger.log("d", "DiscoverRepetierAction: webcams: %s",
                                   len(json_data["webcams"]))
                        if len(json_data["webcams"]) > 0:
                            if "dynamicUrl" in json_data["webcams"][0]:
                                Logger.log(
                                    "d",
                                    "DiscoverRepetierAction: Checking streamurl"
                                )
                                stream_url = json_data["webcams"][0][
                                    "dynamicUrl"].replace(
                                        "127.0.0.1",
                                        re.findall(r'[0-9]+(?:\.[0-9]+){3}',
                                                   reply.url().toString())[0])
                                Logger.log(
                                    "d",
                                    "DiscoverRepetierAction: stream_url: %s",
                                    stream_url)
                                Logger.log(
                                    "d",
                                    "DiscoverRepetierAction: reply_url: %s",
                                    reply.url())
                                if stream_url:  #not empty string or None
                                    self._instance_supports_camera = True
                elif http_status_code == 401:
                    Logger.log("d", "Invalid API key for Repetier.")
                    self._instance_api_key_accepted = False
                    self._instance_in_error = True

                self._instance_responded = True
                self.selectedInstanceSettingsChanged.emit()

    def _createRequest(self,
                       url: str,
                       basic_auth_username: str = "",
                       basic_auth_password: str = "") -> QNetworkRequest:
        request = QNetworkRequest(url)
        request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
        request.setRawHeader(b"User-Agent", self._user_agent)

        if basic_auth_username and basic_auth_password:
            data = base64.b64encode(
                ("%s:%s" % (basic_auth_username,
                            basic_auth_password)).encode()).decode("utf-8")
            request.setRawHeader(b"Authorization",
                                 ("Basic %s" % data).encode())

        # ignore SSL errors (eg for self-signed certificates)
        ssl_configuration = QSslConfiguration.defaultConfiguration()
        ssl_configuration.setPeerVerifyMode(QSslSocket.VerifyNone)
        request.setSslConfiguration(ssl_configuration)

        return request

    ##  Utility handler to base64-decode a string (eg an obfuscated API key), if it has been encoded before
    def _deobfuscateString(self, source: str) -> str:
        try:
            return base64.b64decode(source.encode("ascii")).decode("ascii")
        except UnicodeDecodeError:
            return source

    def _getInstanceInfo(
        self, instance_id: str
    ) -> Tuple[Optional[RepetierOutputDevice], str, str, str]:
        if not self._network_plugin:
            return (None, "", "", "")
        instance = self._network_plugin.getInstanceById(instance_id)
        if not instance:
            return (None, "", "", "")

        return (instance, instance.baseURL, instance.getProperty("userName"),
                instance.getProperty("password"))
예제 #11
0
class MyWindowClass(QtWidgets.QMainWindow, form_auto.Ui_MainWindow):
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)
        self.setupUi(self)
        self.setupNet()
        self.login_status = False
        self.Username = '******'
        self.Usercart = []
        self.Shoplist = []
        self.last_error_msg = None

        actRefresh = QtWidgets.QAction(QIcon('icon_refresh.png'), 'Refresh',
                                       self)
        actRefresh.triggered.connect(self.refresh)
        actRegister = QtWidgets.QAction(QIcon('icon_reg.png'), 'Register',
                                        self)
        actRegister.triggered.connect(self.regDialog)
        actLogin = QtWidgets.QAction(QIcon('icon_login.png'), 'Log in', self)
        actLogin.triggered.connect(self.loginDialog)
        actLogout = QtWidgets.QAction(QIcon('icon_logout.png'), 'Log out',
                                      self)
        actLogout.triggered.connect(self.logoutDialog)
        actQuit = QtWidgets.QAction(QIcon('icon_quit.png'), 'Quit', self)
        actQuit.triggered.connect(self.clExit)
        self.actUsername = QtWidgets.QAction(self.Username, self)
        self.actCart = QtWidgets.QAction(QIcon('icon_cart.png'), 'Cart', self)
        self.actCart.setEnabled(False)
        self.actCart.triggered.connect(self.cartPrepare)

        self.toolbar = self.addToolBar('Tools')
        self.toolbar.addAction(actRefresh)
        self.toolbar.addAction(actLogin)
        self.toolbar.addAction(self.actUsername)
        self.toolbar.addAction(self.actCart)

        self.menuUser.addAction(actLogin)
        self.menuUser.addAction(self.actCart)
        self.menuUser.addSeparator()
        self.menuUser.addAction(actLogout)
        self.menuQuit.addAction(actRegister)
        self.menuQuit.addSeparator()
        self.menuQuit.addAction(actQuit)
        self.menubar.addAction(self.menuQuit.menuAction())
        self.menubar.addAction(self.menuUser.menuAction())

        self.getContent()
        #self.populateTable()
        self.statusBar().showMessage('Ready.')

    def setupNet(self):
        self.manager = QNetworkAccessManager()
        self.url_list = {
            "login": "******",
            "register": "http://127.0.0.1:8000/shop_site/cl_reg/",
            "content": "http://127.0.0.1:8000/shop_site/cl_index/",
            "logout": "http://127.0.0.1:8000/shop_site/cl_logout/",
            "cart": "http://127.0.0.1:8000/shop_site/cl_cart/",
            "checkout": "http://127.0.0.1:8000/shop_site/cl_check/"
        }

    def regDialog(self):
        dr = QtWidgets.QDialog(parent=self)
        dr.setWindowTitle("Register in Nothing Shop")
        dr.setWindowModality(QtCore.Qt.ApplicationModal)
        dr_layoutV = QtWidgets.QVBoxLayout()
        label_name = QtWidgets.QLabel(dr)
        label_name.setText('Enter your name:')
        dr_layoutV.addWidget(label_name)
        text_name = QtWidgets.QLineEdit(dr)
        dr_layoutV.addWidget(text_name)
        label_pass1 = QtWidgets.QLabel(dr)
        label_pass1.setText('Enter your password:'******'Enter your password again:')
        dr_layoutV.addWidget(label_pass2)
        text_pass2 = QtWidgets.QLineEdit(dr)
        text_pass2.setEchoMode(2)
        dr_layoutV.addWidget(text_pass2)
        dr_layoutH = QtWidgets.QHBoxLayout()
        submit = QtWidgets.QPushButton("Register", dr)
        dr_layoutH.addWidget(submit)
        cancel = QtWidgets.QPushButton("Cancel", dr)
        dr_layoutH.addWidget(cancel)
        dr_layoutV.addLayout(dr_layoutH)
        submit.clicked.connect(
            partial(self.register, text_name, text_pass1, text_pass2))
        cancel.clicked.connect(dr.close)
        dr.setLayout(dr_layoutV)
        dr.exec_()

    def register(self, name, pass1, pass2):
        print("Registering...")
        if not name.text() or not pass1.text() or not pass2.text():
            msg = QtWidgets.QMessageBox.warning(
                self, 'Unfilled fields',
                'You didn\'t fill all required fields!',
                QtWidgets.QMessageBox.Ok)
            return
        if pass1.text() != pass2.text():
            msg = QtWidgets.QMessageBox.warning(
                self, 'Passwords mismatch',
                'Passwords entered in both fields are not same!',
                QtWidgets.QMessageBox.Ok)
            return
        self.statusBar().showMessage('Registering...')
        url = QtCore.QUrl(self.url_list['register'])
        request = QNetworkRequest()
        request.setUrl(url)
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          "application/x-www-form-urlencoded")
        data = QtCore.QByteArray()
        data.append(''.join(['user='******'&']))
        data.append(''.join(['password='******'Registration failed', 'Registration failed due to:\n' +
                bytes(self.replyObjectReg.readAll()).decode("utf-8"),
                QtWidgets.QMessageBox.Ok)
            self.statusBar().showMessage('Registration failed.')
            return
        elif self.replyObjectReg.error() != QNetworkReply.NoError:
            msg = QtWidgets.QMessageBox.warning(
                self, 'Registration failed', ''.join([
                    'An error was encountered during connecting to shop server.\nError code: ',
                    str(self.replyObjectReg.error())
                ]), QtWidgets.QMessageBox.Ok)
            self.statusBar().showMessage('An error was encountered.')
            return
        msg = QtWidgets.QMessageBox.information(
            self, 'Registration successful',
            'You are now registered in Nothing Shop!\n You can now use entered username and password to log in!',
            QtWidgets.QMessageBox.Ok)
        self.findChild(QtWidgets.QDialog).close()

    def loginDialog(self):
        if self.login_status:
            msg = QtWidgets.QMessageBox.information(
                self, 'Information',
                ''.join(['You are already logged in as:\n',
                         self.Username]), QtWidgets.QMessageBox.Ok)
        else:
            dl = QtWidgets.QDialog(parent=self)
            dl.setWindowTitle("Log in Nothing Shop")
            dl.setWindowModality(QtCore.Qt.ApplicationModal)
            dl.setFixedSize(220, 110)
            dl_layoutV = QtWidgets.QVBoxLayout()
            text_log = QtWidgets.QLineEdit(dl)
            text_log.setPlaceholderText('Enter your username here...')
            dl_layoutV.addWidget(text_log)
            text_pass = QtWidgets.QLineEdit(dl)
            text_pass.setEchoMode(2)
            text_pass.setPlaceholderText('Enter your password here...')
            dl_layoutV.addWidget(text_pass)
            dl_layoutH = QtWidgets.QHBoxLayout()
            submit = QtWidgets.QPushButton("Login", dl)
            dl_layoutH.addWidget(submit)
            cancel = QtWidgets.QPushButton("Cancel", dl)
            dl_layoutH.addWidget(cancel)
            dl_layoutV.addLayout(dl_layoutH)
            submit.clicked.connect(partial(self.login, text_log, text_pass))
            cancel.clicked.connect(dl.close)
            dl.setLayout(dl_layoutV)
            dl.exec_()

    def login(self, name_field, pass_field):
        print("Logging in...")
        self.statusBar().showMessage('Logging in...')
        self.Username = name_field.text()
        url = QtCore.QUrl(self.url_list['login'])
        request = QNetworkRequest()
        request.setUrl(url)
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          "application/x-www-form-urlencoded")
        data = QtCore.QByteArray()
        data.append(''.join(['user='******'&']))
        data.append(''.join(['password='******'Login failed',
                'Wrong username or password.\nPlease try again.',
                QtWidgets.QMessageBox.Ok)
            self.statusBar().showMessage('Login attempt failed.')
            return
        elif self.replyObjectLogin.error() != QNetworkReply.NoError:
            msg = QtWidgets.QMessageBox.warning(
                self, 'Login failed', ''.join([
                    'An error was encountered during connecting to shop server.\n Error code: ',
                    str(self.replyObjectReg.error())
                ]), QtWidgets.QMessageBox.Ok)
            self.statusBar().showMessage('An error was encountered.')
            return
        self.user_token = bytes(
            self.replyObjectLogin.readAll()).decode("utf-8")
        self.login_status = True
        self.findChild(QtWidgets.QDialog).close()
        self.actUsername.setText(''.join(['Logged in as ', self.Username]))
        self.actCart.setEnabled(True)
        self.statusBar().showMessage('Logged in.')

    def logoutDialog(self):
        if self.login_status:
            msg = QtWidgets.QMessageBox.question(
                self, 'Confirmation', 'Are you sure you want to log out?',
                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
                QtWidgets.QMessageBox.No)
            if msg == QtWidgets.QMessageBox.Yes:
                self.user_token = None
                self.login_status = False
                self.Username = '******'
                self.Usercart = []
                self.actUsername.setText(self.Username)
                self.actCart.setEnabled(False)
                self.statusBar().showMessage('Logged out.')
        else:
            msg = QtWidgets.QMessageBox.question(
                self, 'Information', 'You are already logged out!',
                QtWidgets.QMessageBox.Ok)

    def refresh(self):
        try:
            self.scrollAreaWidgetContents.setParent(None)
        except AttributeError:
            pass
        self.getContent()

    def getContent(self):
        print("Getting content...")
        url = QtCore.QUrl(self.url_list['content'])
        request = QNetworkRequest()
        request.setUrl(url)
        self.replyObject = self.manager.get(request)
        self.replyObject.finished.connect(self.populateShopList)

    def populateShopList(self):
        print('Populating list...')
        if self.replyObject.error() == QNetworkReply.ConnectionRefusedError:
            msg = QtWidgets.QMessageBox.warning(
                self, 'Shoplist fetching failed',
                'Shop server is currently refusing connections.\nCheck your internet connection.',
                QtWidgets.QMessageBox.Ok)
            self.statusBar().showMessage("Couldn't connect to shop server.")
            return
        elif self.replyObject.error() != QNetworkReply.NoError:
            msg = QtWidgets.QMessageBox.warning(
                self, 'Shoplist fetching failed', ''.join([
                    'An error was encountered during connecting to shop server.\n Error code: ',
                    str(self.replyObject.error())
                ]), QtWidgets.QMessageBox.Ok)
            self.statusBar().showMessage('An error was encountered.')
            return
        answerAsJson = bytes(self.replyObject.readAll()).decode("utf-8")
        try:
            answerAsText = json.loads(answerAsJson)
        except json.decoder.JSONDecodeError:
            msg = QtWidgets.QMessageBox.warning(
                self, 'Shoplist fetching failed',
                'An error was encountered while parsing shoplist data.\nPlease try again.',
                QtWidgets.QMessageBox.Ok)
            return
        self.Shoplist.clear()
        for item in answerAsText:
            self.Shoplist.append(
                Merch(item['id'], item['name'], item['desc'], item['quantity'],
                      item['price']))
            data = urllib.request.urlopen(''.join(
                ['http://127.0.0.1:8000/static/', item['image']]))
            self.Shoplist[-1].image = QtGui.QPixmap()
            self.Shoplist[-1].image.loadFromData(data.read())
        self.populateTable()

    def populateTable(self):
        print("Making tables...")
        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 620, 419))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        self.merchList.setWidget(self.scrollAreaWidgetContents)

        self.mainVerticalLayer = QtWidgets.QVBoxLayout(
            self.scrollAreaWidgetContents)
        self.mainVerticalLayer.setObjectName("mainVerticalLayer")

        MerchBoxList = []
        ImageLabelList = []
        NameLabelList = []
        DescBoxList = []
        RemainLabelList = []
        PriceLabelList = []
        MerchNumList = []
        BuyButtonList = []
        MerchBoxHWidgetList = []
        MerchBoxH1LayoutList = []
        MerchBoxH2LayoutList = []
        MerchBoxV1LayoutList = []
        MerchBoxV2LayoutList = []
        for merch in self.Shoplist:
            MerchBoxList.append(
                QtWidgets.QGroupBox(self.scrollAreaWidgetContents))
            MerchBoxList[-1].setTitle(merch.name)

            MerchBoxHWidgetList.append(QtWidgets.QWidget(MerchBoxList[-1]))
            # Columnt 1 - Image
            MerchBoxH1LayoutList.append(
                QtWidgets.QHBoxLayout(MerchBoxHWidgetList[-1]))
            MerchBoxH1LayoutList[-1].setSizeConstraint(
                QtWidgets.QLayout.SetDefaultConstraint)
            MerchBoxH1LayoutList[-1].setContentsMargins(0, 0, 10, 6)
            MerchBoxH1LayoutList[-1].setSpacing(10)

            ImageLabelList.append(QtWidgets.QLabel(MerchBoxHWidgetList[-1]))
            ImageLabelList[-1].setMinimumSize(QtCore.QSize(150, 150))
            ImageLabelList[-1].setAlignment(QtCore.Qt.AlignCenter)
            ImageLabelList[-1].setPixmap(
                merch.image.scaled(ImageLabelList[-1].size(), 1))
            MerchBoxH1LayoutList[-1].addWidget(ImageLabelList[-1])

            # Column 2 - Name, description
            MerchBoxV1LayoutList.append(QtWidgets.QVBoxLayout())
            MerchBoxV1LayoutList[-1].setSizeConstraint(
                QtWidgets.QLayout.SetDefaultConstraint)
            MerchBoxV1LayoutList[-1].setContentsMargins(0, 0, 10, 10)
            MerchBoxV1LayoutList[-1].setSpacing(10)

            NameLabelList.append(QtWidgets.QLabel(MerchBoxHWidgetList[-1]))
            NameLabelList[-1].setText(merch.name)
            MerchBoxV1LayoutList[-1].addWidget(NameLabelList[-1])

            DescBoxList.append(QtWidgets.QTextEdit(MerchBoxHWidgetList[-1]))
            DescBoxList[-1].setPlainText(merch.desc)
            DescBoxList[-1].setReadOnly(True)
            MerchBoxV1LayoutList[-1].addWidget(DescBoxList[-1])
            MerchBoxH1LayoutList[-1].addLayout(MerchBoxV1LayoutList[-1])

            # Column 3 - Remaining, Price, Put in cart controls
            MerchBoxV2LayoutList.append(QtWidgets.QVBoxLayout())
            MerchBoxV2LayoutList[-1].setSizeConstraint(
                QtWidgets.QLayout.SetDefaultConstraint)
            MerchBoxV2LayoutList[-1].setContentsMargins(0, 0, 10, 10)
            MerchBoxV2LayoutList[-1].setSpacing(10)

            RemainLabelList.append(QtWidgets.QLabel(MerchBoxHWidgetList[-1]))
            RemainLabelList[-1].setAlignment(QtCore.Qt.AlignCenter)
            RemainLabelList[-1].setText('Remaining: ' + str(merch.quant))
            MerchBoxV2LayoutList[-1].addWidget(RemainLabelList[-1])

            PriceLabelList.append(QtWidgets.QLabel(MerchBoxHWidgetList[-1]))
            PriceLabelList[-1].setAlignment(QtCore.Qt.AlignCenter)
            PriceLabelList[-1].setText('Price: $' + str(merch.price))
            MerchBoxV2LayoutList[-1].addWidget(PriceLabelList[-1])

            MerchBoxH2LayoutList.append(QtWidgets.QHBoxLayout())
            MerchBoxH2LayoutList[-1].setSizeConstraint(
                QtWidgets.QLayout.SetDefaultConstraint)
            MerchBoxH2LayoutList[-1].setContentsMargins(0, 0, 0, 0)
            MerchBoxH2LayoutList[-1].setSpacing(10)

            MerchNumList.append(QtWidgets.QSpinBox(MerchBoxHWidgetList[-1]))
            MerchNumList[-1].setMinimum(1)
            MerchNumList[-1].setMaximum(merch.quant)
            MerchBoxH2LayoutList[-1].addWidget(MerchNumList[-1])

            BuyButtonList.append(QtWidgets.QPushButton(
                MerchBoxHWidgetList[-1]))
            BuyButtonList[-1].setText("In cart")
            BuyButtonList[-1].clicked.connect(
                partial(self.putCart, merch, MerchNumList[-1], True))
            if merch.quant == 0:
                BuyButtonList[-1].setEnabled(False)
            MerchBoxH2LayoutList[-1].addWidget(BuyButtonList[-1])
            MerchBoxV2LayoutList[-1].addLayout(MerchBoxH2LayoutList[-1])
            MerchBoxH1LayoutList[-1].addLayout(MerchBoxV2LayoutList[-1])

            MerchBoxV1LayoutList[-1].addStretch(1)
            MerchBoxH2LayoutList[-1].addStretch(1)
            MerchBoxH1LayoutList[-1].setStretch(0, 3)
            MerchBoxH1LayoutList[-1].setStretch(1, 8)
            MerchBoxH1LayoutList[-1].setStretch(2, 2)
            MerchBoxList[-1].setLayout(MerchBoxH1LayoutList[-1])
            self.mainVerticalLayer.addWidget(MerchBoxList[-1])
        self.merchList.setWidget(self.scrollAreaWidgetContents)
        self.horizontalLayout_3.addWidget(self.merchList)

    def cartPrepare(self):
        print("Fetching user cart...")
        self.statusBar().showMessage('Fetching user cart...')
        url = QtCore.QUrl(self.url_list['cart'])
        request = QNetworkRequest()
        request.setUrl(url)
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          "application/x-www-form-urlencoded")
        data = QtCore.QByteArray()
        data.append(''.join(['token=', self.user_token, '&']))
        data.append(''.join(['submethod=', 'get']))
        self.replyObjectCart = self.manager.post(request, data)
        self.replyObjectCart.finished.connect(self.cartDialog)

    def cartDialog(self):
        if self.replyObjectCart.error(
        ) == QNetworkReply.AuthenticationRequiredError:
            msg = QtWidgets.QMessageBox.warning(
                self, 'Login error', ''.join([
                    'There was a problem with your credentials.\nPlease try logging in again.\n Error code: ',
                    str(self.replyObjectCart.error())
                ]), QtWidgets.QMessageBox.Ok)
            self.statusBar().showMessage("Couldn't fetch user cart.")
            return
        answerAsJson = bytes(self.replyObjectCart.readAll()).decode("utf-8")
        answerAsText = json.loads(answerAsJson)
        self.Usercart.clear()
        for item in answerAsText:
            self.Usercart.append({
                "merch":
                next(x for x in self.Shoplist if x.id == item['id']),
                "num":
                item['num']
            })
        dc = QtWidgets.QDialog(parent=self)
        dc.setWindowTitle(''.join(["Shop Cart - ", self.Username]))
        dc.setWindowModality(QtCore.Qt.ApplicationModal)
        #dc.resize(600, 420)
        dc_layoutV = QtWidgets.QVBoxLayout()
        lazylist = []
        BoxList = []
        BoxWidgetList = []
        BoxLayoutList = []
        ImgList = []
        NameList = []
        NumList = []
        NumEditList = []
        RemButtonList = []
        if self.Usercart:
            for item in self.Usercart:
                BoxList.append(QtWidgets.QGroupBox(dc))
                BoxList[-1].setObjectName(''.join(
                    ['Box', str(item['merch'].id)]))
                BoxWidgetList.append(QtWidgets.QWidget(BoxList[-1]))
                BoxLayoutList.append(QtWidgets.QHBoxLayout(BoxWidgetList[-1]))
                BoxLayoutList[-1].setSizeConstraint(
                    QtWidgets.QLayout.SetDefaultConstraint)
                BoxLayoutList[-1].setContentsMargins(5, 5, 5, 5)
                BoxLayoutList[-1].setSpacing(10)

                ImgList.append(QtWidgets.QLabel(BoxWidgetList[-1]))
                ImgList[-1].setMinimumSize(QtCore.QSize(50, 50))
                ImgList[-1].setAlignment(QtCore.Qt.AlignCenter)
                ImgList[-1].setPixmap(item['merch'].image.scaled(
                    ImgList[-1].size(), 1))
                BoxLayoutList[-1].addWidget(ImgList[-1])

                NameList.append(QtWidgets.QLabel(BoxWidgetList[-1]))
                NameList[-1].setAlignment(QtCore.Qt.AlignCenter)
                NameList[-1].setText(item['merch'].name)
                BoxLayoutList[-1].addWidget(NameList[-1])

                NumList.append(QtWidgets.QLabel(BoxWidgetList[-1]))
                NumList[-1].setAlignment(QtCore.Qt.AlignCenter)
                NumList[-1].setText(''.join(
                    ['In cart: ', str(item['num']), ' units']))
                NumList[-1].setObjectName(''.join(
                    ['NumLabel', str(item['merch'].id)]))
                BoxLayoutList[-1].addWidget(NumList[-1])

                NumEditList.append(QtWidgets.QSpinBox(BoxWidgetList[-1]))
                NumEditList[-1].setMinimum(1)
                NumEditList[-1].setMaximum(item['num'])
                BoxLayoutList[-1].addWidget(NumEditList[-1])

                RemButtonList.append(QtWidgets.QPushButton(BoxWidgetList[-1]))
                RemButtonList[-1].setText('Remove from cart')
                RemButtonList[-1].clicked.connect(
                    partial(self.putCart, item['merch'], NumEditList[-1],
                            False))
                BoxLayoutList[-1].addWidget(RemButtonList[-1])

                BoxList[-1].setLayout(BoxLayoutList[-1])
                dc_layoutV.addWidget(BoxList[-1])
        else:
            nothinglabel = QtWidgets.QLabel(dc)
            nothinglabel.setText('Your cart is empty!')
            dc_layoutV.addWidget(nothinglabel)
        submitbtn = QtWidgets.QPushButton("Purchase!", dc)
        submitbtn.clicked.connect(self.purchaseDialog)
        submitbtn.setObjectName('submitbtn')
        if not self.Usercart:
            submitbtn.setEnabled(False)
        dc_layoutV.addWidget(submitbtn)
        cancelbtn = QtWidgets.QPushButton("Close cart...", dc)
        dc_layoutV.addWidget(cancelbtn)
        cancelbtn.clicked.connect(dc.close)
        dc.setLayout(dc_layoutV)
        dc.exec_()

    def putCart(self, merch, num_field, add):
        print("Putting merchandize in cart...")
        self.statusBar().showMessage('Putting merchandize in cart...')
        if self.login_status:
            url = QtCore.QUrl(self.url_list['cart'])
            request = QNetworkRequest()
            request.setUrl(url)
            request.setHeader(QNetworkRequest.ContentTypeHeader,
                              "application/x-www-form-urlencoded")
            data = QtCore.QByteArray()
            data.append(''.join(['token=', self.user_token, '&']))
            data.append(''.join(['submethod=', 'change', '&']))
            data.append(''.join(['merchid=', str(merch.id), '&']))
            if add:
                data.append(''.join(['merchnum=', str(num_field.value())]))
            else:
                data.append(''.join(['merchnum=-', str(num_field.value())]))
            self.replyObjectPutCart = self.manager.post(request, data)
            self.replyObjectPutCart.finished.connect(self.putCartResult)
        else:
            msg = QtWidgets.QMessageBox.information(
                self, 'Information',
                'You need to log in first\nto use shopping cart!',
                QtWidgets.QMessageBox.Ok)
            self.statusBar().showMessage('Not logged in yet.')

    def putCartResult(self):
        print("A thing is happening!")
        if self.replyObjectPutCart.error(
        ) == QNetworkReply.AuthenticationRequiredError:
            self.statusBar().showMessage("Authentication failed.")
            msg = QtWidgets.QMessageBox.warning(
                self, 'Authentication failed',
                'Your login session has expired.\nPlease relog into shop and try again.',
                QtWidgets.QMessageBox.Ok)
            return
        elif self.replyObjectPutCart.error() != QNetworkReply.NoError:
            msg = QtWidgets.QMessageBox.warning(
                self, 'Authentication failed', ''.join([
                    'An error was encountered during connecting to shop server.\n Error code: ',
                    str(self.replyObjectPutCart.error())
                ]), QtWidgets.QMessageBox.Ok)
            self.statusBar().showMessage('An error was encountered.')
            return
        answerAsJson = bytes(self.replyObjectPutCart.readAll()).decode("utf-8")
        answerAsText = json.loads(answerAsJson)
        print(answerAsText)
        if 'merch+' in answerAsText:
            print("A merch+ is happening!")
            next(x for x in self.Shoplist if x.id ==
                 answerAsText['merch+']).quant = answerAsText['quantity']
            self.scrollAreaWidgetContents.setParent(None)
            self.populateTable()
            self.statusBar().showMessage('Placed merchandize in cart.')
        elif 'merch-' in answerAsText:
            print("A merch- is happening!")
            item = next(x for x in self.Usercart
                        if x['merch'].id == answerAsText['merch-'])
            item['num'] -= answerAsText['quantity']
            item['merch'].quant += answerAsText['quantity']
            dc = self.findChild(QtWidgets.QDialog)
            if item['num'] < 1:
                dc.findChild(QtWidgets.QGroupBox,
                             ''.join(['Box', str(item['merch'].id)])).hide()
                self.Usercart.remove(item)
                if not self.Usercart:
                    dc.findChild(QtWidgets.QPushButton,
                                 'submitbtn').setEnabled(False)
            else:
                label = dc.findChild(
                    QtWidgets.QLabel,
                    ''.join(['NumLabel', str(item['merch'].id)]))
                label.setText(''.join(
                    ['In cart: ', str(item['num']), ' units']))
            self.scrollAreaWidgetContents.setParent(None)
            self.populateTable()
            self.statusBar().showMessage('Removed merchandize from cart.')

    def purchaseDialog(self):
        dp = QtWidgets.QDialog(parent=self)
        dp.setWindowTitle("Checkout")
        dp.setWindowModality(QtCore.Qt.ApplicationModal)
        dp_layoutV = QtWidgets.QVBoxLayout()
        label_address = QtWidgets.QLabel(dp)
        label_address.setText('Enter your address:')
        dp_layoutV.addWidget(label_address)
        text_address = QtWidgets.QTextEdit(dp)
        dp_layoutV.addWidget(text_address)
        label_date = QtWidgets.QLabel(dp)
        label_date.setText(
            'Enter date when you want to receive your purchase:')
        dp_layoutV.addWidget(label_date)
        text_date = QtWidgets.QLineEdit(dp)
        dp_layoutV.addWidget(text_date)
        label_mail = QtWidgets.QLabel(dp)
        label_mail.setText('Enter your mail to receive confirmation letter:')
        dp_layoutV.addWidget(label_mail)
        text_mail = QtWidgets.QLineEdit(dp)
        dp_layoutV.addWidget(text_mail)
        dp_layoutH = QtWidgets.QHBoxLayout()
        submit = QtWidgets.QPushButton("Purchase!", dp)
        dp_layoutH.addWidget(submit)
        cancel = QtWidgets.QPushButton("Cancel", dp)
        dp_layoutH.addWidget(cancel)
        dp_layoutV.addLayout(dp_layoutH)
        submit.clicked.connect(self.purchase)
        cancel.clicked.connect(dp.close)
        dp.setLayout(dp_layoutV)
        dp.exec_()

    def purchase(self):
        print("Confirming purchase...")
        self.statusBar().showMessage('Confirming purchase...')
        url = QtCore.QUrl(self.url_list['checkout'])
        request = QNetworkRequest()
        request.setUrl(url)
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          "application/x-www-form-urlencoded")
        data = QtCore.QByteArray()
        data.append(''.join(['token=', self.user_token]))
        self.replyObjectPur = self.manager.post(request, data)
        self.replyObjectPur.finished.connect(self.purchaseFinalize)

    def purchaseFinalize(self):
        if self.replyObjectPur.error(
        ) == QNetworkReply.AuthenticationRequiredError:
            self.statusBar().showMessage("Authentication failed.")
            msg = QtWidgets.QMessageBox.warning(
                self, 'Authentication failed',
                'Your login session has expired.\nPlease relog into shop and try again.',
                QtWidgets.QMessageBox.Ok)
            return
        elif self.replyObjectPur.error() != QNetworkReply.NoError:
            msg = QtWidgets.QMessageBox.warning(
                self, 'Confirmation failed', ''.join([
                    'An error was encountered during connecting to shop server.\n Error code: ',
                    str(self.replyObjectPutCart.error())
                ]), QtWidgets.QMessageBox.Ok)
            self.statusBar().showMessage('An error was encountered.')
            return
        msg = QtWidgets.QMessageBox.information(
            self, 'Purchase completed!',
            'Congratulations! Your merchandize is already on it\'s way!',
            QtWidgets.QMessageBox.Ok)
        for window in self.findChildren(QtWidgets.QDialog):
            window.close()

    def clExit(self):
        app.quit()
예제 #12
0
class OctoPrintOutputDevice(PrinterOutputDevice):
    def __init__(self, key, address, port, properties):
        super().__init__(key)

        self._address = address
        self._port = port
        self._path = properties.get(b"path", b"/").decode("utf-8")
        if self._path[-1:] != "/":
            self._path += "/"
        self._key = key
        self._properties = properties  # Properties dict as provided by zero conf

        self._gcode = None
        self._auto_print = True

        ##  We start with a single extruder, but update this when we get data from octoprint
        self._num_extruders_set = False
        self._num_extruders = 1

        self._api_version = "1"
        self._api_prefix = "api/"
        self._api_header = "X-Api-Key"
        self._api_key = None

        protocol = "https" if properties.get(b'useHttps') == b"true" else "http"
        self._base_url = "%s://%s:%d%s" % (protocol, self._address, self._port, self._path)
        self._api_url = self._base_url + self._api_prefix
        self._camera_url = "%s://%s:8080/?action=stream" % (protocol, self._address)

        self.setPriority(2) # Make sure the output device gets selected above local file output
        self.setName(key)
        self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print with OctoPrint"))
        self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print with OctoPrint"))
        self.setIconName("print")
        self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected to OctoPrint on {0}").format(self._key))

        #   QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
        #   hook itself into the event loop, which results in events never being fired / done.
        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onRequestFinished)

        ##  Hack to ensure that the qt networking stuff isn't garbage collected (unless we want it to)
        self._printer_request = None
        self._printer_reply = None

        self._print_job_request = None
        self._print_job_reply = None

        self._image_request = None
        self._image_reply = None
        self._stream_buffer = b""
        self._stream_buffer_start_index = -1

        self._post_request = None
        self._post_reply = None
        self._post_multi_part = None
        self._post_part = None

        self._job_request = None
        self._job_reply = None

        self._command_request = None
        self._command_reply = None

        self._progress_message = None
        self._error_message = None
        self._connection_message = None

        self._update_timer = QTimer()
        self._update_timer.setInterval(2000)  # TODO; Add preference for update interval
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)

        self._camera_image_id = 0
        self._camera_image = QImage()

        self._connection_state_before_timeout = None

        self._last_response_time = None
        self._last_request_time = None
        self._response_timeout_time = 5
        self._recreate_network_manager_time = 30 # If we have no connection, re-create network manager every 30 sec.
        self._recreate_network_manager_count = 1

        self._preheat_timer = QTimer()
        self._preheat_timer.setSingleShot(True)
        self._preheat_timer.timeout.connect(self.cancelPreheatBed)

    def getProperties(self):
        return self._properties

    @pyqtSlot(str, result = str)
    def getProperty(self, key):
        key = key.encode("utf-8")
        if key in self._properties:
            return self._properties.get(key, b"").decode("utf-8")
        else:
            return ""

    ##  Get the unique key of this machine
    #   \return key String containing the key of the machine.
    @pyqtSlot(result = str)
    def getKey(self):
        return self._key

    ##  Set the API key of this OctoPrint instance
    def setApiKey(self, api_key):
        self._api_key = api_key

    ##  Name of the instance (as returned from the zeroConf properties)
    @pyqtProperty(str, constant = True)
    def name(self):
        return self._key

    ##  Version (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def octoprintVersion(self):
        return self._properties.get(b"version", b"").decode("utf-8")

    ## IPadress of this instance
    @pyqtProperty(str, constant=True)
    def ipAddress(self):
        return self._address

    ## port of this instance
    @pyqtProperty(int, constant=True)
    def port(self):
        return self._port

    ## path of this instance
    @pyqtProperty(str, constant=True)
    def path(self):
        return self._path

    ## absolute url of this instance
    @pyqtProperty(str, constant=True)
    def baseURL(self):
        return self._base_url

    def _startCamera(self):
        global_container_stack = Application.getInstance().getGlobalContainerStack()
        if not global_container_stack or not parseBool(global_container_stack.getMetaDataEntry("octoprint_show_camera", False)):
            return

        # Start streaming mjpg stream
        url = QUrl(self._camera_url)
        self._image_request = QNetworkRequest(url)
        self._image_reply = self._manager.get(self._image_request)
        self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)

    def _stopCamera(self):
        if self._image_reply:
            self._image_reply.abort()
            self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
            self._image_reply = None
        self._image_request = None

        self._stream_buffer = b""
        self._stream_buffer_start_index = -1

        self._camera_image = QImage()
        self.newImage.emit()

    def _update(self):
        if self._last_response_time:
            time_since_last_response = time() - self._last_response_time
        else:
            time_since_last_response = 0
        if self._last_request_time:
            time_since_last_request = time() - self._last_request_time
        else:
            time_since_last_request = float("inf") # An irrelevantly large number of seconds

        # Connection is in timeout, check if we need to re-start the connection.
        # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows.
        # Re-creating the QNetworkManager seems to fix this issue.
        if self._last_response_time and self._connection_state_before_timeout:
            if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count:
                self._recreate_network_manager_count += 1
                # It can happen that we had a very long timeout (multiple times the recreate time).
                # In that case we should jump through the point that the next update won't be right away.
                while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time:
                    self._recreate_network_manager_count += 1
                Logger.log("d", "Timeout lasted over 30 seconds (%.1fs), re-checking connection.", time_since_last_response)
                self._createNetworkManager()
                return

        # Check if we have an connection in the first place.
        if not self._manager.networkAccessible():
            if not self._connection_state_before_timeout:
                Logger.log("d", "The network connection seems to be disabled. Going into timeout mode")
                self._connection_state_before_timeout = self._connection_state
                self.setConnectionState(ConnectionState.error)
                self._connection_message = Message(i18n_catalog.i18nc("@info:status",
                                                                      "The connection with the network was lost."))
                self._connection_message.show()
                # Check if we were uploading something. Abort if this is the case.
                # Some operating systems handle this themselves, others give weird issues.
                try:
                    if self._post_reply:
                        Logger.log("d", "Stopping post upload because the connection was lost.")
                        try:
                            self._post_reply.uploadProgress.disconnect(self._onUploadProgress)
                        except TypeError:
                            pass  # The disconnection can fail on mac in some cases. Ignore that.

                        self._post_reply.abort()
                        self._progress_message.hide()
                except RuntimeError:
                    self._post_reply = None  # It can happen that the wrapped c++ object is already deleted.
            return
        else:
            if not self._connection_state_before_timeout:
                self._recreate_network_manager_count = 1

        # Check that we aren't in a timeout state
        if self._last_response_time and self._last_request_time and not self._connection_state_before_timeout:
            if time_since_last_response > self._response_timeout_time and time_since_last_request <= self._response_timeout_time:
                # Go into timeout state.
                Logger.log("d", "We did not receive a response for %s seconds, so it seems OctoPrint is no longer accesible.", time() - self._last_response_time)
                self._connection_state_before_timeout = self._connection_state
                self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with OctoPrint was lost. Check your network-connections."))
                self._connection_message.show()
                self.setConnectionState(ConnectionState.error)

        ## Request 'general' printer data
        url = QUrl(self._api_url + "printer")
        self._printer_request = QNetworkRequest(url)
        self._printer_request.setRawHeader(self._api_header.encode(), self._api_key.encode())
        self._printer_reply = self._manager.get(self._printer_request)

        ## Request print_job data
        url = QUrl(self._api_url + "job")
        self._job_request = QNetworkRequest(url)
        self._job_request.setRawHeader(self._api_header.encode(), self._api_key.encode())
        self._job_reply = self._manager.get(self._job_request)

    def _createNetworkManager(self):
        if self._manager:
            self._manager.finished.disconnect(self._onRequestFinished)

        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onRequestFinished)

    def close(self):
        self._updateJobState("")
        self.setConnectionState(ConnectionState.closed)
        if self._progress_message:
            self._progress_message.hide()
        if self._error_message:
            self._error_message.hide()
        self._update_timer.stop()

        self._stopCamera()

    def requestWrite(self, node, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
        self.writeStarted.emit(self)
        self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")

        self.startPrint()

    def isConnected(self):
        return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error

    ##  Start requesting data from the instance
    def connect(self):
        self._createNetworkManager()

        self.setConnectionState(ConnectionState.connecting)
        self._update()  # Manually trigger the first update, as we don't want to wait a few secs before it starts.
        Logger.log("d", "Connection with instance %s with url %s started", self._key, self._base_url)
        self._update_timer.start()

        self._last_response_time = None
        self.setAcceptsCommands(False)
        self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connecting to OctoPrint on {0}").format(self._key))

    ##  Stop requesting data from the instance
    def disconnect(self):
        Logger.log("d", "Connection with instance %s with url %s stopped", self._key, self._base_url)
        self.close()

    newImage = pyqtSignal()

    @pyqtProperty(QUrl, notify = newImage)
    def cameraImage(self):
        self._camera_image_id += 1
        # There is an image provider that is called "camera". In order to ensure that the image qml object, that
        # requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
        # as new (instead of relying on cached version and thus forces an update.
        temp = "image://camera/" + str(self._camera_image_id)
        return QUrl(temp, QUrl.TolerantMode)

    def getCameraImage(self):
        return self._camera_image

    def _setJobState(self, job_state):
        if job_state == "abort":
            command = "cancel"
        elif job_state == "print":
            if self.jobState == "paused":
                command = "pause"
            else:
                command = "start"
        elif job_state == "pause":
            command = "pause"

        if command:
            self._sendJobCommand(command)

    def startPrint(self):
        global_container_stack = Application.getInstance().getGlobalContainerStack()
        if not global_container_stack:
            return

        if self.jobState not in ["ready", ""]:
            if self.jobState == "offline":
                self._error_message = Message(i18n_catalog.i18nc("@info:status", "OctoPrint is offline. Unable to start a new job."))
            else:
                self._error_message = Message(i18n_catalog.i18nc("@info:status", "OctoPrint is busy. Unable to start a new job."))
            self._error_message.show()
            return

        self._preheat_timer.stop()

        self._auto_print = parseBool(global_container_stack.getMetaDataEntry("octoprint_auto_print", True))
        if self._auto_print:
            Application.getInstance().showPrintMonitor.emit(True)

        try:
            self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to OctoPrint"), 0, False, -1)
            self._progress_message.addAction("Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
            self._progress_message.actionTriggered.connect(self._cancelSendGcode)
            self._progress_message.show()

            ## Mash the data into single string
            single_string_file_data = ""
            last_process_events = time()
            for line in self._gcode:
                single_string_file_data += line
                if time() > last_process_events + 0.05:
                    # Ensure that the GUI keeps updated at least 20 times per second.
                    QCoreApplication.processEvents()
                    last_process_events = time()

            job_name = Application.getInstance().getPrintInformation().jobName.strip()
            if job_name is "":
                job_name = "untitled_print"
            file_name = "%s.gcode" % job_name

            ##  Create multi_part request
            self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)

            ##  Create parts (to be placed inside multipart)
            self._post_part = QHttpPart()
            self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"select\"")
            self._post_part.setBody(b"true")
            self._post_multi_part.append(self._post_part)

            if self._auto_print:
                self._post_part = QHttpPart()
                self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"print\"")
                self._post_part.setBody(b"true")
                self._post_multi_part.append(self._post_part)

            self._post_part = QHttpPart()
            self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name)
            self._post_part.setBody(single_string_file_data.encode())
            self._post_multi_part.append(self._post_part)

            destination = "local"
            if parseBool(global_container_stack.getMetaDataEntry("octoprint_store_sd", False)):
                destination = "sdcard"

            url = QUrl(self._api_url + "files/" + destination)

            ##  Create the QT request
            self._post_request = QNetworkRequest(url)
            self._post_request.setRawHeader(self._api_header.encode(), self._api_key.encode())

            ##  Post request + data
            self._post_reply = self._manager.post(self._post_request, self._post_multi_part)
            self._post_reply.uploadProgress.connect(self._onUploadProgress)

            self._gcode = None

        except IOError:
            self._progress_message.hide()
            self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to send data to OctoPrint."))
            self._error_message.show()
        except Exception as e:
            self._progress_message.hide()
            Logger.log("e", "An exception occurred in network connection: %s" % str(e))

    def _cancelSendGcode(self, message_id, action_id):
        if self._post_reply:
            Logger.log("d", "Stopping upload because the user pressed cancel.")
            try:
                self._post_reply.uploadProgress.disconnect(self._onUploadProgress)
            except TypeError:
                pass  # The disconnection can fail on mac in some cases. Ignore that.

            self._post_reply.abort()
            self._post_reply = None
        self._progress_message.hide()

    def _sendCommand(self, command):
        self._sendCommandToApi("printer/command", command)
        Logger.log("d", "Sent gcode command to OctoPrint instance: %s", command)

    def _sendJobCommand(self, command):
        self._sendCommandToApi("job", command)
        Logger.log("d", "Sent job command to OctoPrint instance: %s", command)

    def _sendCommandToApi(self, endpoint, command):
        url = QUrl(self._api_url + endpoint)
        self._command_request = QNetworkRequest(url)
        self._command_request.setRawHeader(self._api_header.encode(), self._api_key.encode())
        self._command_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")

        data = "{\"command\": \"%s\"}" % command
        self._command_reply = self._manager.post(self._command_request, data.encode())

    ##  Pre-heats the heated bed of the printer.
    #
    #   \param temperature The temperature to heat the bed to, in degrees
    #   Celsius.
    #   \param duration How long the bed should stay warm, in seconds.
    @pyqtSlot(float, float)
    def preheatBed(self, temperature, duration):
        self._setTargetBedTemperature(temperature)
        if duration > 0:
            self._preheat_timer.setInterval(duration * 1000)
            self._preheat_timer.start()
        else:
            self._preheat_timer.stop()

    ##  Cancels pre-heating the heated bed of the printer.
    #
    #   If the bed is not pre-heated, nothing happens.
    @pyqtSlot()
    def cancelPreheatBed(self):
        self._setTargetBedTemperature(0)
        self._preheat_timer.stop()

    def _setTargetBedTemperature(self, temperature):
        Logger.log("d", "Setting bed temperature to %s", temperature)
        self._sendCommand("M140 S%s" % temperature)

    def _setTargetHotendTemperature(self, index, temperature):
        Logger.log("d", "Setting hotend %s temperature to %s", index, temperature)
        self._sendCommand("M104 T%s S%s" % (index, temperature))

    def _setHeadPosition(self, x, y , z, speed):
        self._sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))

    def _setHeadX(self, x, speed):
        self._sendCommand("G0 X%s F%s" % (x, speed))

    def _setHeadY(self, y, speed):
        self._sendCommand("G0 Y%s F%s" % (y, speed))

    def _setHeadZ(self, z, speed):
        self._sendCommand("G0 Y%s F%s" % (z, speed))

    def _homeHead(self):
        self._sendCommand("G28")

    def _homeBed(self):
        self._sendCommand("G28 Z")

    def _moveHead(self, x, y, z, speed):
        self._sendCommand("G91")
        self._sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
        self._sendCommand("G90")

    ##  Handler for all requests that have finished.
    def _onRequestFinished(self, reply):
        if reply.error() == QNetworkReply.TimeoutError:
            Logger.log("w", "Received a timeout on a request to the instance")
            self._connection_state_before_timeout = self._connection_state
            self.setConnectionState(ConnectionState.error)
            return

        if self._connection_state_before_timeout and reply.error() == QNetworkReply.NoError:  #  There was a timeout, but we got a correct answer again.
            if self._last_response_time:
                Logger.log("d", "We got a response from the instance after %s of silence", time() - self._last_response_time)
            self.setConnectionState(self._connection_state_before_timeout)
            self._connection_state_before_timeout = None

        if reply.error() == QNetworkReply.NoError:
            self._last_response_time = time()

        http_status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        if not http_status_code:
            # Received no or empty reply
            return

        if reply.operation() == QNetworkAccessManager.GetOperation:
            if self._api_prefix + "printer" in reply.url().toString():  # Status update from /printer.
                if http_status_code == 200:
                    if not self.acceptsCommands:
                        self.setAcceptsCommands(True)
                        self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected to OctoPrint on {0}").format(self._key))

                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)
                    json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))

                    if "temperature" in json_data:
                        if not self._num_extruders_set:
                            self._num_extruders = 0
                            while "tool%d" % self._num_extruders in json_data["temperature"]:
                                self._num_extruders = self._num_extruders + 1

                            # Reinitialise from PrinterOutputDevice to match the new _num_extruders
                            self._hotend_temperatures = [0] * self._num_extruders
                            self._target_hotend_temperatures = [0] * self._num_extruders

                            self._num_extruders_set = True

                        # Check for hotend temperatures
                        for index in range(0, self._num_extruders):
                            temperature = json_data["temperature"]["tool%d" % index]["actual"] if ("tool%d" % index) in json_data["temperature"] else 0
                            self._setHotendTemperature(index, temperature)

                        bed_temperature = json_data["temperature"]["bed"]["actual"] if "bed" in json_data["temperature"] else 0
                        self._setBedTemperature(bed_temperature)

                    job_state = "offline"
                    if "state" in json_data:
                        if json_data["state"]["flags"]["error"]:
                            job_state = "error"
                        elif json_data["state"]["flags"]["paused"]:
                            job_state = "paused"
                        elif json_data["state"]["flags"]["printing"]:
                            job_state = "printing"
                        elif json_data["state"]["flags"]["ready"]:
                            job_state = "ready"
                    self._updateJobState(job_state)

                elif http_status_code == 401:
                    self._updateJobState("offline")
                    self.setConnectionText(i18n_catalog.i18nc("@info:status", "OctoPrint on {0} does not allow access to print").format(self._key))
                elif http_status_code == 409:
                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)

                    self._updateJobState("offline")
                    self.setConnectionText(i18n_catalog.i18nc("@info:status", "The printer connected to OctoPrint on {0} is not operational").format(self._key))
                else:
                    self._updateJobState("offline")
                    Logger.log("w", "Received an unexpected returncode: %d", http_status_code)

            elif self._api_prefix + "job" in reply.url().toString():  # Status update from /job:
                if http_status_code == 200:
                    json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))

                    progress = json_data["progress"]["completion"]
                    if progress:
                        self.setProgress(progress)

                    if json_data["progress"]["printTime"]:
                        self.setTimeElapsed(json_data["progress"]["printTime"])
                        if json_data["progress"]["printTimeLeft"]:
                            self.setTimeTotal(json_data["progress"]["printTime"] + json_data["progress"]["printTimeLeft"])
                        elif json_data["job"]["estimatedPrintTime"]:
                            self.setTimeTotal(max(json_data["job"]["estimatedPrintTime"], json_data["progress"]["printTime"]))
                        elif progress > 0:
                            self.setTimeTotal(json_data["progress"]["printTime"] / (progress / 100))
                        else:
                            self.setTimeTotal(0)
                    else:
                        self.setTimeElapsed(0)
                        self.setTimeTotal(0)
                    self.setJobName(json_data["job"]["file"]["name"])
                else:
                    pass  # TODO: Handle errors

        elif reply.operation() == QNetworkAccessManager.PostOperation:
            if self._api_prefix + "files" in reply.url().toString():  # Result from /files command:
                if http_status_code == 201:
                    Logger.log("d", "Resource created on OctoPrint instance: %s", reply.header(QNetworkRequest.LocationHeader).toString())
                else:
                    pass  # TODO: Handle errors

                reply.uploadProgress.disconnect(self._onUploadProgress)
                self._progress_message.hide()
                global_container_stack = Application.getInstance().getGlobalContainerStack()
                if not self._auto_print:
                    location = reply.header(QNetworkRequest.LocationHeader)
                    if location:
                        file_name = QUrl(reply.header(QNetworkRequest.LocationHeader).toString()).fileName()
                        message = Message(i18n_catalog.i18nc("@info:status", "Saved to OctoPrint as {0}").format(file_name))
                    else:
                        message = Message(i18n_catalog.i18nc("@info:status", "Saved to OctoPrint"))
                    message.addAction("open_browser", i18n_catalog.i18nc("@action:button", "Open OctoPrint..."), "globe",
                                        i18n_catalog.i18nc("@info:tooltip", "Open the OctoPrint web interface"))
                    message.actionTriggered.connect(self._onMessageActionTriggered)
                    message.show()

            elif self._api_prefix + "job" in reply.url().toString():  # Result from /job command:
                if http_status_code == 204:
                    Logger.log("d", "Octoprint command accepted")
                else:
                    pass  # TODO: Handle errors

        else:
            Logger.log("d", "OctoPrintOutputDevice got an unhandled operation %s", reply.operation())

    def _onStreamDownloadProgress(self, bytes_received, bytes_total):
        self._stream_buffer += self._image_reply.readAll()

        if self._stream_buffer_start_index == -1:
            self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
        stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')

        if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
            jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
            self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
            self._stream_buffer_start_index = -1

            self._camera_image.loadFromData(jpg_data)
            self.newImage.emit()

    def _onUploadProgress(self, bytes_sent, bytes_total):
        if bytes_total > 0:
            # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
            # timeout responses if this happens.
            self._last_response_time = time()

            progress = bytes_sent / bytes_total * 100
            if progress < 100:
                if progress > self._progress_message.getProgress():
                    self._progress_message.setProgress(progress)
            else:
                self._progress_message.hide()
                self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Storing data on OctoPrint"), 0, False, -1)
                self._progress_message.show()
        else:
            self._progress_message.setProgress(0)

    def _onMessageActionTriggered(self, message, action):
        if action == "open_browser":
            QDesktopServices.openUrl(QUrl(self._base_url))
class OctoPrintOutputDevice(PrinterOutputDevice):
    def __init__(self, key, address, properties):
        super().__init__(key)
        self._address = address
        self._key = key
        self._properties = properties  # Properties dict as provided by zero conf

        self._gcode = None

        ##  Todo: Hardcoded value now; we should probably read this from the machine definition and octoprint.
        self._num_extruders = 1

        self._hotend_temperatures = [0] * self._num_extruders
        self._target_hotend_temperatures = [0] * self._num_extruders

        self._api_version = "1"
        self._api_prefix = "/api/"
        self._api_header = "X-Api-Key"
        self._api_key = None

        self.setName(key)
        self.setShortDescription(
            i18n_catalog.i18nc("@action:button", "Print with OctoPrint"))
        self.setDescription(
            i18n_catalog.i18nc("@properties:tooltip", "Print with OctoPrint"))
        self.setIconName("print")

        #   QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
        #   hook itself into the event loop, which results in events never being fired / done.
        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onFinished)

        ##  Hack to ensure that the qt networking stuff isn't garbage collected (unless we want it to)
        self._printer_request = None
        self._printer_reply = None

        self._print_job_request = None
        self._print_job_reply = None

        self._image_request = None
        self._image_reply = None

        self._post_request = None
        self._post_reply = None
        self._post_multi_part = None
        self._post_part = None

        self._job_request = None
        self._job_reply = None

        self._progress_message = None
        self._error_message = None

        self._update_timer = QTimer()
        self._update_timer.setInterval(
            2000)  # TODO; Add preference for update interval
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)

        self._camera_timer = QTimer()
        self._camera_timer.setInterval(
            500)  # Todo: Add preference for camera update interval
        self._camera_timer.setSingleShot(False)
        self._camera_timer.timeout.connect(self._update_camera)

        self._camera_image_id = 0

        self._camera_image = QImage()

    def getProperties(self):
        return self._properties

    ##  Get the unique key of this machine
    #   \return key String containing the key of the machine.
    @pyqtSlot(result=str)
    def getKey(self):
        return self._key

    ##  Set the API key of this OctoPrint instance
    def setApiKey(self, api_key):
        self._api_key = api_key

    ##  Name of the printer (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def name(self):
        return self._key

    ##  Version (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def octoprintVersion(self):
        return self._properties.get(b"version", b"").decode("utf-8")

    ## IPadress of this printer
    @pyqtProperty(str, constant=True)
    def ipAddress(self):
        return self._address

    def _update_camera(self):
        ## Request new image
        url = QUrl("http://" + self._address + ":8080/?action=snapshot")
        self._image_request = QNetworkRequest(url)
        self._image_reply = self._manager.get(self._image_request)

    def _update(self):
        ## Request 'general' printer data
        url = QUrl("http://" + self._address + self._api_prefix + "printer")
        self._printer_request = QNetworkRequest(url)
        self._printer_request.setRawHeader(self._api_header.encode(),
                                           self._api_key.encode())
        self._printer_reply = self._manager.get(self._printer_request)

        ## Request print_job data
        url = QUrl("http://" + self._address + self._api_prefix + "job")
        self._job_request = QNetworkRequest(url)
        self._job_request.setRawHeader(self._api_header.encode(),
                                       self._api_key.encode())
        self._job_reply = self._manager.get(self._job_request)

    def close(self):
        self.setConnectionState(ConnectionState.closed)
        self._update_timer.stop()
        self._camera_timer.stop()

    def requestWrite(self, node, file_name=None, filter_by_machine=False):
        self._gcode = getattr(
            Application.getInstance().getController().getScene(), "gcode_list")

        self.startPrint()

    def isConnected(self):
        return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error

    ##  Start requesting data from printer
    def connect(self):
        self.setConnectionState(ConnectionState.connecting)
        self._update(
        )  # Manually trigger the first update, as we don't want to wait a few secs before it starts.
        self._update_camera()
        Logger.log("d", "Connection with printer %s with ip %s started",
                   self._key, self._address)
        self._update_timer.start()
        self._camera_timer.start()

    newImage = pyqtSignal()

    @pyqtProperty(QUrl, notify=newImage)
    def cameraImage(self):
        self._camera_image_id += 1
        temp = "image://camera/" + str(self._camera_image_id)
        return QUrl(temp, QUrl.TolerantMode)

    def getCameraImage(self):
        return self._camera_image

    def _setJobState(self, job_state):
        url = QUrl("http://" + self._address + self._api_prefix + "job")
        self._job_request = QNetworkRequest(url)
        self._job_request.setRawHeader(self._api_header.encode(),
                                       self._api_key.encode())
        self._job_request.setHeader(QNetworkRequest.ContentTypeHeader,
                                    "application/json")
        if job_state == "abort":
            command = "cancel"
        elif job_state == "print":
            if self.jobState == "paused":
                command = "pause"
            else:
                command = "start"
        elif job_state == "pause":
            command = "pause"
        data = "{\"command\": \"%s\"}" % command
        self._job_reply = self._manager.post(self._job_request, data.encode())
        Logger.log("d", "Sent command to OctoPrint instance: %s", data)

    def startPrint(self):
        if self.jobState != "ready" and self.jobState != "":
            self._error_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Printer is printing. Unable to start a new job."))
            self._error_message.show()
            return
        try:
            self._progress_message = Message(
                i18n_catalog.i18nc("@info:status", "Sending data to printer"),
                0, False, -1)
            self._progress_message.show()

            ## Mash the data into single string
            single_string_file_data = ""
            for line in self._gcode:
                single_string_file_data += line

            ##  TODO: Use correct file name (we use placeholder now)
            file_name = "%s.gcode" % Application.getInstance(
            ).getPrintInformation().jobName

            ##  Create multi_part request
            self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)

            ##  Create parts (to be placed inside multipart)
            self._post_part = QHttpPart()
            self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                                      "form-data; name=\"select\"")
            self._post_part.setBody(b"true")
            self._post_multi_part.append(self._post_part)

            self._post_part = QHttpPart()
            self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                                      "form-data; name=\"print\"")
            self._post_part.setBody(b"true")
            self._post_multi_part.append(self._post_part)

            self._post_part = QHttpPart()
            self._post_part.setHeader(
                QNetworkRequest.ContentDispositionHeader,
                "form-data; name=\"file\"; filename=\"%s\"" % file_name)
            self._post_part.setBody(single_string_file_data.encode())
            self._post_multi_part.append(self._post_part)

            url = QUrl("http://" + self._address + self._api_prefix +
                       "files/local")

            ##  Create the QT request
            self._post_request = QNetworkRequest(url)
            self._post_request.setRawHeader(self._api_header.encode(),
                                            self._api_key.encode())

            ##  Post request + data
            self._post_reply = self._manager.post(self._post_request,
                                                  self._post_multi_part)
            self._post_reply.uploadProgress.connect(self._onUploadProgress)

            self._gcode = None

        except IOError:
            self._progress_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Unable to send data to printer. Is another job still active?"
                ))
            self._error_message.show()
        except Exception as e:
            self._progress_message.hide()
            Logger.log(
                "e",
                "An exception occurred in network connection: %s" % str(e))

    ##  Handler for all requests that have finished.
    def _onFinished(self, reply):
        http_status_code = reply.attribute(
            QNetworkRequest.HttpStatusCodeAttribute)
        if reply.operation() == QNetworkAccessManager.GetOperation:
            if "printer" in reply.url().toString(
            ):  # Status update from /printer.
                if http_status_code == 200:
                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)
                    json_data = json.loads(
                        bytes(reply.readAll()).decode("utf-8"))

                    # Check for hotend temperatures
                    for index in range(0, self._num_extruders):
                        temperature = json_data["temperature"]["tool%d" %
                                                               index]["actual"]
                        self._setHotendTemperature(index, temperature)

                    bed_temperature = json_data["temperature"]["bed"]["actual"]
                    self._setBedTemperature(bed_temperature)

                    printer_state = "offline"
                    if json_data["state"]["flags"]["error"]:
                        printer_state = "error"
                    elif json_data["state"]["flags"]["paused"]:
                        printer_state = "paused"
                    elif json_data["state"]["flags"]["printing"]:
                        printer_state = "printing"
                    elif json_data["state"]["flags"]["ready"]:
                        printer_state = "ready"
                    self._updateJobState(printer_state)
                else:
                    pass  # TODO: Handle errors

            elif "job" in reply.url().toString():  # Status update from /job:
                if http_status_code == 200:
                    json_data = json.loads(
                        bytes(reply.readAll()).decode("utf-8"))

                    progress = json_data["progress"]["completion"]
                    if progress:
                        self.setProgress(progress)

                    if json_data["progress"]["printTime"]:
                        self.setTimeElapsed(json_data["progress"]["printTime"])
                        if json_data["progress"]["printTimeLeft"]:
                            self.setTimeTotal(
                                json_data["progress"]["printTime"] +
                                json_data["progress"]["printTimeLeft"])
                        elif json_data["job"]["estimatedPrintTime"]:
                            self.setTimeTotal(
                                json_data["job"]["estimatedPrintTime"])
                        elif progress > 0:
                            self.setTimeTotal(
                                json_data["progress"]["printTime"] /
                                (progress / 100))
                        else:
                            self.setTimeTotal(0)
                    else:
                        self.setTimeElapsed(0)
                        self.setTimeTotal(0)
                    self.setJobName(json_data["job"]["file"]["name"])
                else:
                    pass  # TODO: Handle errors

            elif "snapshot" in reply.url().toString():  # Update from camera:
                if reply.attribute(
                        QNetworkRequest.HttpStatusCodeAttribute) == 200:
                    self._camera_image.loadFromData(reply.readAll())
                    self.newImage.emit()
                else:
                    pass  # TODO: Handle errors

        elif reply.operation() == QNetworkAccessManager.PostOperation:
            if "files" in reply.url().toString(
            ):  # Result from /files command:
                if http_status_code == 201:
                    Logger.log(
                        "d", "Resource created on OctoPrint instance: %s",
                        reply.header(
                            QNetworkRequest.LocationHeader).toString())
                else:
                    pass  # TODO: Handle errors

                reply.uploadProgress.disconnect(self._onUploadProgress)
                self._progress_message.hide()

            elif "job" in reply.url().toString():  # Result from /job command:
                if http_status_code == 204:
                    Logger.log("d", "Octoprint command accepted")
                else:
                    pass  # TODO: Handle errors

        else:
            Logger.log("d",
                       "OctoPrintOutputDevice got an unhandled operation %s",
                       reply.operation())

    def _onUploadProgress(self, bytes_sent, bytes_total):
        if bytes_total > 0:
            self._progress_message.setProgress(bytes_sent / bytes_total * 100)
        else:
            self._progress_message.setProgress(0)
예제 #14
0
class RepetierServerOutputDevice(PrinterOutputDevice):
    def __init__(self, key, address, port, properties):
        super().__init__(key)

        self._address = address
        self._port = port
        self._path = properties["path"] if "path" in properties else "/"
        self._key = key
        self._properties = properties  # Properties dict as provided by zero conf

        self._gcode = None
        self._auto_print = True

        ##  Todo: Hardcoded value now; we should probably read this from the machine definition and Repetier-Server.
        self._num_extruders_set = False
        self._num_extruders = 1
        self._slug = "fanera1"
        self._api_version = "1"
        self._api_prefix = "printer/api/" + self._slug
        self._api_header = "X-Api-Key"
        self._api_key = None

        self._base_url = "http://%s:%d%s" % (self._address, self._port,
                                             self._path)
        self._api_url = self._base_url + self._api_prefix
        self._model_url = self._base_url + "printer/model/" + self._slug + "?a=upload"
        self._camera_url = "http://%s:8080/?action=snapshot" % self._address

        self.setPriority(
            2
        )  # Make sure the output device gets selected above local file output
        self.setName(key)
        self.setShortDescription(
            i18n_catalog.i18nc("@action:button", "Print with Repetier-Server"))
        self.setDescription(
            i18n_catalog.i18nc("@properties:tooltip",
                               "Print with Repetier-Server"))
        self.setIconName("print")
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connected to Repetier-Server on {0}").format(
                                   self._key))

        #   QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
        #   hook itself into the event loop, which results in events never being fired / done.
        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onRequestFinished)

        ##  Hack to ensure that the qt networking stuff isn't garbage collected (unless we want it to)
        self._printer_request = None
        self._printer_reply = None

        self._print_job_request = None
        self._print_job_reply = None

        self._image_request = None
        self._image_reply = None

        self._post_request = None
        self._post_reply = None
        self._post_multi_part = None
        self._post_part = None

        self._job_request = None
        self._job_reply = None

        self._command_request = None
        self._command_reply = None

        self._progress_message = None
        self._error_message = None
        self._connection_message = None

        self._update_timer = QTimer()
        self._update_timer.setInterval(
            2000)  # TODO; Add preference for update interval
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)

        self._camera_timer = QTimer()
        self._camera_timer.setInterval(
            500)  # Todo: Add preference for camera update interval
        self._camera_timer.setSingleShot(False)
        self._camera_timer.timeout.connect(self._update_camera)

        self._camera_image_id = 0

        self._camera_image = QImage()

        self._connection_state_before_timeout = None

        self._last_response_time = None
        self._last_request_time = None
        self._response_timeout_time = 5
        self._recreate_network_manager_time = 30  # If we have no connection, re-create network manager every 30 sec.
        self._recreate_network_manager_count = 1

    def getProperties(self):
        return self._properties

    @pyqtSlot(str, result=str)
    def getProperty(self, key):
        key = key.encode("utf-8")
        if key in self._properties:
            return self._properties.get(key, b"").decode("utf-8")
        else:
            return ""

    ##  Get the unique key of this machine
    #   \return key String containing the key of the machine.
    @pyqtSlot(result=str)
    def getKey(self):
        return self._key

    ##  Set the API key of this Repetier-Server instance
    def setApiKey(self, api_key):
        self._api_key = api_key

    ##  Name of the instance (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def name(self):
        return self._key

    ##  Version (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def octoprintVersion(self):
        return self._properties.get(b"version", b"").decode("utf-8")

    ## IPadress of this instance
    @pyqtProperty(str, constant=True)
    def ipAddress(self):
        return self._address

    ## port of this instance
    @pyqtProperty(int, constant=True)
    def port(self):
        return self._port

    ## path of this instance
    @pyqtProperty(str, constant=True)
    def path(self):
        return self._path

    ## absolute url of this instance
    @pyqtProperty(str, constant=True)
    def baseURL(self):
        return self._base_url

    def _update_camera(self):
        ## Request new image
        url = QUrl(self._camera_url)
        self._image_request = QNetworkRequest(url)
        self._image_reply = self._manager.get(self._image_request)

    def _update(self):
        if self._last_response_time:
            time_since_last_response = time() - self._last_response_time
        else:
            time_since_last_response = 0
        if self._last_request_time:
            time_since_last_request = time() - self._last_request_time
        else:
            time_since_last_request = float(
                "inf")  # An irrelevantly large number of seconds

        # Connection is in timeout, check if we need to re-start the connection.
        # Sometimes the qNetwork manager incorrectly reports the network status on Mac & Windows.
        # Re-creating the QNetworkManager seems to fix this issue.
        if self._last_response_time and self._connection_state_before_timeout:
            if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count:
                self._recreate_network_manager_count += 1
                # It can happen that we had a very long timeout (multiple times the recreate time).
                # In that case we should jump through the point that the next update won't be right away.
                while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time:
                    self._recreate_network_manager_count += 1
                Logger.log(
                    "d",
                    "Timeout lasted over 30 seconds (%.1fs), re-checking connection.",
                    time_since_last_response)
                self._createNetworkManager()
                return

        # Check if we have an connection in the first place.
        if not self._manager.networkAccessible():
            if not self._connection_state_before_timeout:
                Logger.log(
                    "d",
                    "The network connection seems to be disabled. Going into timeout mode"
                )
                self._connection_state_before_timeout = self._connection_state
                self.setConnectionState(ConnectionState.error)
                self._connection_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "The connection with the network was lost."))
                self._connection_message.show()
                # Check if we were uploading something. Abort if this is the case.
                # Some operating systems handle this themselves, others give weird issues.
                try:
                    if self._post_reply:
                        Logger.log(
                            "d",
                            "Stopping post upload because the connection was lost."
                        )
                        try:
                            self._post_reply.uploadProgress.disconnect(
                                self._onUploadProgress)
                        except TypeError:
                            pass  # The disconnection can fail on mac in some cases. Ignore that.

                        self._post_reply.abort()
                        self._progress_message.hide()
                except RuntimeError:
                    self._post_reply = None  # It can happen that the wrapped c++ object is already deleted.
            return
        else:
            if not self._connection_state_before_timeout:
                self._recreate_network_manager_count = 1

        # Check that we aren't in a timeout state
        if self._last_response_time and self._last_request_time and not self._connection_state_before_timeout:
            if time_since_last_response > self._response_timeout_time and time_since_last_request <= self._response_timeout_time:
                # Go into timeout state.
                Logger.log(
                    "d",
                    "We did not receive a response for %s seconds, so it seems Repetier-Server is no longer accesible.",
                    time() - self._last_response_time)
                self._connection_state_before_timeout = self._connection_state
                self._connection_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "The connection with Repetier-Server was lost. Check your network-connections."
                    ))
                self._connection_message.show()
                self.setConnectionState(ConnectionState.error)

        ## Request 'general' printer data
        urlString = self._api_url + "?a=stateList"
        #Logger.log("d","XXX URL: " + urlString)
        url = QUrl(urlString)
        self._printer_request = QNetworkRequest(url)
        self._printer_request.setRawHeader(self._api_header.encode(),
                                           self._api_key.encode())
        self._printer_reply = self._manager.get(self._printer_request)

        ## Request print_job data
        url = QUrl(self._api_url + "?a=listPrinter")
        self._job_request = QNetworkRequest(url)
        self._job_request.setRawHeader(self._api_header.encode(),
                                       self._api_key.encode())
        self._job_reply = self._manager.get(self._job_request)

    def _createNetworkManager(self):
        if self._manager:
            self._manager.finished.disconnect(self._onRequestFinished)

        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onRequestFinished)

    def close(self):
        self._updateJobState("")
        self.setConnectionState(ConnectionState.closed)
        if self._progress_message:
            self._progress_message.hide()
        if self._error_message:
            self._error_message.hide()
        self._update_timer.stop()
        self._camera_timer.stop()
        self._camera_image = QImage()
        self.newImage.emit()

    def requestWrite(self, node, file_name=None, filter_by_machine=False):
        self.writeStarted.emit(self)
        self._gcode = getattr(
            Application.getInstance().getController().getScene(), "gcode_list")

        self.startPrint()

    def isConnected(self):
        return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error

    ##  Start requesting data from the instance
    def connect(self):
        #self.close()  # Ensure that previous connection (if any) is killed.
        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        if not global_container_stack:
            return

        self._createNetworkManager()

        self.setConnectionState(ConnectionState.connecting)
        self._update(
        )  # Manually trigger the first update, as we don't want to wait a few secs before it starts.

        Logger.log("d", "Connection with instance %s with ip %s started",
                   self._key, self._address)
        self._update_timer.start()

        if parseBool(
                global_container_stack.getMetaDataEntry(
                    "octoprint_show_camera", False)):
            self._update_camera()
            self._camera_timer.start()
        else:
            self._camera_timer.stop()
            self._camera_image = QImage()
            self.newImage.emit()

        self._last_response_time = None
        self.setAcceptsCommands(False)
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connecting to Repetier-Server on {0}").format(
                                   self._key))

    ##  Stop requesting data from the instance
    def disconnect(self):
        Logger.log("d", "Connection with instance %s with ip %s stopped",
                   self._key, self._address)
        self.close()

    newImage = pyqtSignal()

    @pyqtProperty(QUrl, notify=newImage)
    def cameraImage(self):
        self._camera_image_id += 1
        # There is an image provider that is called "camera". In order to ensure that the image qml object, that
        # requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
        # as new (instead of relying on cached version and thus forces an update.
        temp = "image://camera/" + str(self._camera_image_id)
        return QUrl(temp, QUrl.TolerantMode)

    def getCameraImage(self):
        return self._camera_image

    def _setJobState(self, job_state):

        # if job_state == "abort":
        #     command = "cancel"
        # elif job_state == "print":
        #     if self.jobState == "paused":
        #         command = "pause"
        #     else:
        #         command = "start"
        # elif job_state == "pause":
        #     command = "pause"

        urlString = ""
        if job_state == "abort":
            command = "cancel"
            urlString = self._api_url + '?a=stopJob'
        elif job_state == "print":
            if self.jobState == "paused":
                command = "pause"
                urlString = self._api_url + '?a=send&data={"cmd":"@pause"}'
            else:
                command = "start"
                urlString = self._api_url + '?a=continueJob'
        elif job_state == "pause":
            command = "pause"
            urlString = self._api_url + '?a=send&data={"cmd":"@pause"}'

        Logger.log("d", "XXX:Command:" + command)
        Logger.log("d", "XXX:Command:" + urlString)

        if urlString:
            url = QUrl(urlString)
            self._printer_request = QNetworkRequest(url)
            self._printer_request.setRawHeader(self._api_header.encode(),
                                               self._api_key.encode())
            self._printer_reply = self._manager.get(self._printer_request)
        # if command:
        #     self._sendCommand(command)

    def startPrint(self):
        global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        if not global_container_stack:
            return

        self._auto_print = parseBool(
            global_container_stack.getMetaDataEntry("repetier_auto_print",
                                                    True))
        if self._auto_print:
            Application.getInstance().showPrintMonitor.emit(True)

        if self.jobState != "ready" and self.jobState != "":
            self._error_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Repetier-Server is printing. Unable to start a new job."))
            self._error_message.show()
            return
        try:
            self._progress_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Sending data to Repetier-Server"), 0,
                False, -1)
            self._progress_message.show()

            ## Mash the data into single string
            single_string_file_data = ""
            last_process_events = time()
            for line in self._gcode:
                single_string_file_data += line
                if time() > last_process_events + 0.05:
                    # Ensure that the GUI keeps updated at least 20 times per second.
                    QCoreApplication.processEvents()
                    last_process_events = time()

            file_name = "%s.gcode" % Application.getInstance(
            ).getPrintInformation().jobName

            ##  Create multi_part request
            self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)

            ##  Create parts (to be placed inside multipart)
            # self._post_part = QHttpPart()
            # self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"select\"")
            # self._post_part.setBody(b"true")
            # self._post_multi_part.append(self._post_part)

            # if self._auto_print:
            #     self._post_part = QHttpPart()
            #     self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"print\"")
            #     self._post_part.setBody(b"true")
            #     self._post_multi_part.append(self._post_part)

            self._post_part = QHttpPart()
            self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                                      "form-data; filename=\"%s\"" % file_name)
            self._post_part.setBody(single_string_file_data.encode())
            self._post_multi_part.append(self._post_part)

            # destination = "local"
            # if parseBool(global_container_stack.getMetaDataEntry("octoprint_store_sd", False)):
            #     destination = "sdcard"
            # TODO ??

            url = QUrl(self._model_url + "&name=" + file_name)

            ##  Create the QT request
            self._post_request = QNetworkRequest(url)
            self._post_request.setRawHeader(self._api_header.encode(),
                                            self._api_key.encode())

            ##  Post request + data
            self._post_reply = self._manager.post(self._post_request,
                                                  self._post_multi_part)
            self._post_reply.uploadProgress.connect(self._onUploadProgress)

            self._gcode = None

        except IOError:
            self._progress_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Unable to send data to Repetier-Server."))
            self._error_message.show()
        except Exception as e:
            self._progress_message.hide()
            Logger.log(
                "e",
                "An exception occurred in network connection: %s" % str(e))

    def _sendCommand(self, command):
        url = QUrl(self._api_url + "job")
        self._command_request = QNetworkRequest(url)
        self._command_request.setRawHeader(self._api_header.encode(),
                                           self._api_key.encode())
        self._command_request.setHeader(QNetworkRequest.ContentTypeHeader,
                                        "application/json")

        data = "{\"command\": \"%s\"}" % command
        self._command_reply = self._manager.post(self._command_request,
                                                 data.encode())
        Logger.log("d", "Sent command to Repetier-Server instance: %s", data)

    def _setTargetBedTemperature(self, temperature):
        Logger.log("d", "Setting bed temperature to %s", temperature)
        self._sendCommand("M140 S%s" % temperature)

    def _setTargetHotendTemperature(self, index, temperature):
        Logger.log("d", "Setting hotend %s temperature to %s", index,
                   temperature)
        self._sendCommand("M104 T%s S%s" % (index, temperature))

    def _setHeadPosition(self, x, y, z, speed):
        self._sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))

    def _setHeadX(self, x, speed):
        self._sendCommand("G0 X%s F%s" % (x, speed))

    def _setHeadY(self, y, speed):
        self._sendCommand("G0 Y%s F%s" % (y, speed))

    def _setHeadZ(self, z, speed):
        self._sendCommand("G0 Y%s F%s" % (z, speed))

    def _homeHead(self):
        self._sendCommand("G28")

    def _homeBed(self):
        self._sendCommand("G28 Z")

    def _moveHead(self, x, y, z, speed):
        self._sendCommand("G91")
        self._sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
        self._sendCommand("G90")

    ##  Handler for all requests that have finished.
    def _onRequestFinished(self, reply):
        if reply.error() == QNetworkReply.TimeoutError:
            Logger.log("w", "Received a timeout on a request to the instance")
            self._connection_state_before_timeout = self._connection_state
            self.setConnectionState(ConnectionState.error)
            return

        if self._connection_state_before_timeout and reply.error(
        ) == QNetworkReply.NoError:  #  There was a timeout, but we got a correct answer again.
            if self._last_response_time:
                Logger.log(
                    "d",
                    "We got a response from the instance after %s of silence",
                    time() - self._last_response_time)
            self.setConnectionState(self._connection_state_before_timeout)
            self._connection_state_before_timeout = None

        if reply.error() == QNetworkReply.NoError:
            self._last_response_time = time()

        http_status_code = reply.attribute(
            QNetworkRequest.HttpStatusCodeAttribute)
        if not http_status_code:
            # Received no or empty reply
            return

        if reply.operation() == QNetworkAccessManager.GetOperation:
            if "stateList" in reply.url().toString(
            ):  # Status update from /printer.
                if http_status_code == 200:
                    if not self.acceptsCommands:
                        self.setAcceptsCommands(True)
                        self.setConnectionText(
                            i18n_catalog.i18nc(
                                "@info:status",
                                "Connected to Repetier-Server on {0}").format(
                                    self._key))

                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)
                    json_data = json.loads(
                        bytes(reply.readAll()).decode("utf-8"))

                    if not self._num_extruders_set:
                        self._num_extruders = 0
                        ## TODO
                        # while "extruder" % self._num_extruders in json_data[self._slug]:
                        #     self._num_extruders = self._num_extruders + 1

                        # Reinitialise from PrinterOutputDevice to match the new _num_extruders
                        # self._hotend_temperatures = [0] * self._num_extruders
                        # self._target_hotend_temperatures = [0] * self._num_extruders
                        # self._num_extruders_set = True
                        # TODO

                    # Check for hotend temperatures
                    # for index in range(0, self._num_extruders):
                    #     temperature = json_data[self._slug]["extruder"]["tempRead"]
                    #     self._setHotendTemperature(index, temperature)
                    temperature = json_data[
                        self._slug]["extruder"][0]["tempRead"]
                    self._setHotendTemperature(0, temperature)

                    bed_temperature = json_data[
                        self._slug]["heatedBed"]["tempRead"]
                    #bed_temperature_set = json_data[self._slug]["heatedBed"]["tempSet"]
                    self._setBedTemperature(bed_temperature)

                elif http_status_code == 401:
                    self.setAcceptsCommands(False)
                    self.setConnectionText(
                        i18n_catalog.i18nc(
                            "@info:status",
                            "Repetier-Server on {0} does not allow access to print"
                        ).format(self._key))
                else:
                    pass  # TODO: Handle errors

            elif "listPrinter" in reply.url().toString(
            ):  # Status update from /job:
                if http_status_code == 200:
                    json_data = json.loads(
                        bytes(reply.readAll()).decode("utf-8"))

                    for printer in json_data:
                        if printer["slug"] == self._slug:

                            job_name = printer["job"]
                            self.setJobName(job_name)

                            job_state = "offline"
                            # if printer["state"]["flags"]["error"]:
                            #     job_state = "error"
                            if printer["paused"] == "true":
                                job_state = "paused"
                            elif job_name != "none":
                                job_state = "printing"
                                self.setProgress(printer["done"])
                            elif job_name == "none":
                                job_state = "ready"
                            self._updateJobState(job_state)

                    # progress = json_data["progress"]["completion"]
                    # if progress:
                    #     self.setProgress(progress)

                    # if json_data["progress"]["printTime"]:
                    #     self.setTimeElapsed(json_data["progress"]["printTime"])
                    #     if json_data["progress"]["printTimeLeft"]:
                    #         self.setTimeTotal(json_data["progress"]["printTime"] + json_data["progress"]["printTimeLeft"])
                    #     elif json_data["job"]["estimatedPrintTime"]:
                    #         self.setTimeTotal(max(json_data["job"]["estimatedPrintTime"], json_data["progress"]["printTime"]))
                    #     elif progress > 0:
                    #         self.setTimeTotal(json_data["progress"]["printTime"] / (progress / 100))
                    #     else:
                    #         self.setTimeTotal(0)
                    # else:
                    #     self.setTimeElapsed(0)
                    #     self.setTimeTotal(0)
                    # self.setJobName(json_data["job"]["file"]["name"])
                else:
                    pass  # TODO: Handle errors

            elif "snapshot" in reply.url().toString():  # Update from camera:
                if http_status_code == 200:
                    self._camera_image.loadFromData(reply.readAll())
                    self.newImage.emit()
                else:
                    pass  # TODO: Handle errors

        elif reply.operation() == QNetworkAccessManager.PostOperation:
            if "model" in reply.url().toString(
            ):  # Result from /files command:
                if http_status_code == 201:
                    Logger.log(
                        "d",
                        "Resource created on Repetier-Server instance: %s",
                        reply.header(
                            QNetworkRequest.LocationHeader).toString())
                else:
                    pass  # TODO: Handle errors

                json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))

                modelList = json_data["data"]

                lastModel = modelList[len(modelList) - 1]
                # Logger.log("d", "XXX1:len"+str(len(modelList)))
                # Logger.log("d", "XXX1:lastModel"+str(lastModel))
                modelId = lastModel["id"]

                # "http://%s:%d%s" % (self._address, self._port, self._path)
                urlString = self._api_url + '?a=copyModel&data={"id": %s}' % (
                    modelId)
                Logger.log("d", "XXX1: modelId: " + str(modelId))
                url = QUrl(urlString)
                self._printer_request = QNetworkRequest(url)
                self._printer_request.setRawHeader(self._api_header.encode(),
                                                   self._api_key.encode())
                self._printer_reply = self._manager.get(self._printer_request)
                Logger.log("d", "XXX1: modelId: " + str(urlString))

                reply.uploadProgress.disconnect(self._onUploadProgress)
                self._progress_message.hide()
                global_container_stack = Application.getInstance(
                ).getGlobalContainerStack()
                if not self._auto_print:
                    location = reply.header(QNetworkRequest.LocationHeader)
                    if location:
                        file_name = QUrl(
                            reply.header(QNetworkRequest.LocationHeader).
                            toString()).fileName()
                        message = Message(
                            i18n_catalog.i18nc(
                                "@info:status",
                                "Saved to Repetier-Server as {0}").format(
                                    file_name))
                    else:
                        message = Message(
                            i18n_catalog.i18nc("@info:status",
                                               "Saved to Repetier-Server"))
                    message.addAction(
                        "open_browser",
                        i18n_catalog.i18nc("@action:button",
                                           "Open Repetier-Server..."), "globe",
                        i18n_catalog.i18nc(
                            "@info:tooltip",
                            "Open the Repetier-Server web interface"))
                    message.actionTriggered.connect(
                        self._onMessageActionTriggered)
                    message.show()

            elif "job" in reply.url().toString():  # Result from /job command:
                if http_status_code == 204:
                    Logger.log("d", "Repetier-Server command accepted")
                else:
                    pass  # TODO: Handle errors

        else:
            Logger.log(
                "d",
                "RepetierServerOutputDevice got an unhandled operation %s",
                reply.operation())

    def _onUploadProgress(self, bytes_sent, bytes_total):
        if bytes_total > 0:
            # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
            # timeout responses if this happens.
            self._last_response_time = time()

            progress = bytes_sent / bytes_total * 100
            if progress < 100:
                if progress > self._progress_message.getProgress():
                    self._progress_message.setProgress(progress)
            else:
                self._progress_message.hide()
                self._progress_message = Message(
                    i18n_catalog.i18nc("@info:status",
                                       "Storing data on Repetier-Server"), 0,
                    False, -1)
                self._progress_message.show()
        else:
            self._progress_message.setProgress(0)

    def _onMessageActionTriggered(self, message, action):
        if action == "open_browser":
            QDesktopServices.openUrl(QUrl(self._base_url))
예제 #15
0
class MyWindowClass(PyQt5.QtWidgets.QMainWindow, form_auto.Ui_MainWindow):
    def __init__(self, parent = None):
        PyQt5.QtWidgets.QMainWindow.__init__(self, parent)
        self.setupUi(self)
        self.setupNet()
        self.login_status = False
        self.Username = '******'
        
        actRefresh = QtWidgets.QAction('Refresh', self)
        actRefresh.triggered.connect(self.refresh)
        
        actLogin = QtWidgets.QAction('Login', self)
        actLogin.triggered.connect(self.loginDialog)
        
        self.actUsername = QtWidgets.QAction(self.Username, self)
        
        self.toolbar = self.addToolBar('Refresh')
        self.toolbar.addAction(actRefresh)
        self.toolbar.addAction(actLogin)
        self.toolbar.addAction(self.actUsername)
        
        self.actionQuit.triggered.connect(self.clExit)
        self.getContent()
        self.statusBar().showMessage('Ready')
    
    def setupNet(self):
        self.manager = QNetworkAccessManager()
        self.url_list = {"login": "******",
                            "content": "http://127.0.0.1:8000/shop_site/cl_index/",
                            "logout": "http://127.0.0.1:8000/shop_site/cl_logout/",
                            "checkout": "http://127.0.0.1:8000/shop_site/cl_checkout/"}
            
    def login(self, name_field, pass_field):
        print("Logging in...")
        self.Username = name_field.text()
        url = PyQt5.QtCore.QUrl(self.url_list['login'])
        request = QNetworkRequest()
        request.setUrl(url)
        request.setHeader(PyQt5.QtNetwork.QNetworkRequest.ContentTypeHeader, "application/x-www-form-urlencoded")
        data = PyQt5.QtCore.QByteArray()
        data.append(''.join(['user='******'&']))
        data.append(''.join(['password='******'Failed')
        elif self.replyObjectLogin.error() == PyQt5.QtNetwork.QNetworkReply.NoError:
            self.user_token = bytes(self.replyObjectLogin.readAll()).decode("utf-8")
            self.login_status = True
            self.actUsername.setText(''.join(['Logged in as ', self.Username]))
            self.statusBar().showMessage('Logged in')
    
    def refresh(self):
        self.scrollAreaWidgetContents.setParent(None)
        self.getContent()
    
    def getContent(self):
        print("Getting content...")
        url = PyQt5.QtCore.QUrl(self.url_list['content'])
        request = QNetworkRequest()
        request.setUrl(url)
        self.replyObject = self.manager.get(request)
        self.replyObject.finished.connect(self.populateTable)
    
    def loginDialog(self):
        dl = PyQt5.QtWidgets.QDialog(parent = self)
        dl.setWindowTitle("Log in Nothing Shop")
        dl.setWindowModality(PyQt5.QtCore.Qt.ApplicationModal)
        dl.setFixedSize(220, 110)
        dl_layoutV = QtWidgets.QVBoxLayout()
        text_log = QtWidgets.QLineEdit(dl)
        text_log.setPlaceholderText('Enter your username here...')
        dl_layoutV.addWidget(text_log)
        text_pass = QtWidgets.QLineEdit(dl)
        text_pass.setEchoMode(2)
        text_pass.setPlaceholderText('Enter your password here...')
        dl_layoutV.addWidget(text_pass)
        dl_layoutH = QtWidgets.QHBoxLayout()
        submit = QtWidgets.QPushButton("Login", dl)
        dl_layoutH.addWidget(submit)
        cancel = QtWidgets.QPushButton("Cancel", dl)
        dl_layoutH.addWidget(cancel)
        dl_layoutV.addLayout(dl_layoutH)
        submit.clicked.connect(partial(self.login, text_log, text_pass))
        cancel.clicked.connect(dl.close)
        dl.setLayout(dl_layoutV)
        dl.exec_()
    
    def populateTable(self):
        answerAsJson = bytes(self.replyObject.readAll()).decode("utf-8")
        answerAsText = json.loads(answerAsJson)
        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 620, 419))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        self.merchList.setWidget(self.scrollAreaWidgetContents)
        
        self.mainVerticalLayer = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents)
        self.mainVerticalLayer.setObjectName("mainVerticalLayer")
        MerchBoxList = []
        ImageLabelList = []
        NameLabelList = []
        DescBoxList = []
        RemainLabelList = []
        MerchNumList = []
        BuyButtonList = []
        MerchBoxHWidgetList = []
        MerchBoxH1LayoutList = []
        MerchBoxH2LayoutList = []
        MerchBoxV1LayoutList = []
        MerchBoxV2LayoutList = []
        pixmap1 = PyQt5.QtGui.QPixmap('no_image.png')
        for merch in answerAsText:
            MerchBoxList.append(QtWidgets.QGroupBox(self.scrollAreaWidgetContents))
            #MerchBoxList[-1].setObjectName("merchBox1")
            MerchBoxList[-1].setTitle(merch['name'])
            MerchBoxHWidgetList.append(QtWidgets.QWidget(MerchBoxList[-1]))
            #MerchBoxHWidgetList[-1].setGeometry(QtCore.QRect(5, 15, 620, 160))
            MerchBoxH1LayoutList.append(QtWidgets.QHBoxLayout(MerchBoxHWidgetList[-1]))
            MerchBoxH1LayoutList[-1].setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
            MerchBoxH1LayoutList[-1].setContentsMargins(0, 0, 10, 6)
            MerchBoxH1LayoutList[-1].setSpacing(10)
            ImageLabelList.append(QtWidgets.QLabel(MerchBoxHWidgetList[-1]))
            ImageLabelList[-1].setMinimumSize(QtCore.QSize(150, 150))
            ImageLabelList[-1].setAlignment(QtCore.Qt.AlignCenter)
            ImageLabelList[-1].setPixmap(pixmap1)
            #self.merchImage1.setObjectName("merchImage1")
            MerchBoxH1LayoutList[-1].addWidget(ImageLabelList[-1])
            
            MerchBoxV1LayoutList.append(QtWidgets.QVBoxLayout())
            MerchBoxV1LayoutList[-1].setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
            MerchBoxV1LayoutList[-1].setContentsMargins(0, 0, 10, 10)
            MerchBoxV1LayoutList[-1].setSpacing(10)
            #MerchBoxV1LayoutList[-1].setObjectName("verticalLayout")
            NameLabelList.append(QtWidgets.QLabel(MerchBoxHWidgetList[-1]))
            NameLabelList[-1].setText(merch['name'])
            #self.nameLabel1.setObjectName("nameLabel1")
            MerchBoxV1LayoutList[-1].addWidget(NameLabelList[-1])
            DescBoxList.append(QtWidgets.QTextEdit(MerchBoxHWidgetList[-1]))
            DescBoxList[-1].setPlainText(merch['desc'])
            DescBoxList[-1].setReadOnly(True)
            #self.merchDesc1.setObjectName("merchDesc1")
            MerchBoxV1LayoutList[-1].addWidget(DescBoxList[-1])
            MerchBoxH1LayoutList[-1].addLayout(MerchBoxV1LayoutList[-1])
            
            MerchBoxV2LayoutList.append(QtWidgets.QVBoxLayout())
            MerchBoxV2LayoutList[-1].setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
            MerchBoxV2LayoutList[-1].setContentsMargins(0, 0, 10, 10)
            MerchBoxV2LayoutList[-1].setSpacing(10)
            #MerchBoxV2LayoutList[-1].setObjectName("verticalLayout_2")
            RemainLabelList.append(QtWidgets.QLabel(MerchBoxHWidgetList[-1]))
            RemainLabelList[-1].setAlignment(QtCore.Qt.AlignCenter)
            RemainLabelList[-1].setText('Remaining: ' + str(merch['quantity']))
            #self.RemainingLabel1.setObjectName("RemainingLabel1")
            MerchBoxV2LayoutList[-1].addWidget(RemainLabelList[-1])
            MerchBoxH2LayoutList.append(QtWidgets.QHBoxLayout())
            MerchBoxH2LayoutList[-1].setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
            MerchBoxH2LayoutList[-1].setContentsMargins(0, 0, 0, 0)
            MerchBoxH2LayoutList[-1].setSpacing(10)
            #self.horizontalLayout.setObjectName("horizontalLayout")
            MerchNumList.append(QtWidgets.QSpinBox(MerchBoxHWidgetList[-1]))
            MerchNumList[-1].setMinimum(1)
            MerchNumList[-1].setMaximum(merch['quantity'])
            #self.merchNum.setObjectName("merchNum")
            MerchBoxH2LayoutList[-1].addWidget(MerchNumList[-1])
            BuyButtonList.append(QtWidgets.QPushButton(MerchBoxHWidgetList[-1]))
            BuyButtonList[-1].setText("Buy")
            #self.BuyButton1.setObjectName("BuyButton1")
            MerchBoxH2LayoutList[-1].addWidget(BuyButtonList[-1])
            MerchBoxV2LayoutList[-1].addLayout(MerchBoxH2LayoutList[-1])
            MerchBoxH1LayoutList[-1].addLayout(MerchBoxV2LayoutList[-1])
            
            MerchBoxV1LayoutList[-1].addStretch(1)
            #MerchBoxV2LayoutList[-1].addStretch(1)
            #MerchBoxH1LayoutList[-1].addStretch(1)
            MerchBoxH2LayoutList[-1].addStretch(1)
            MerchBoxH1LayoutList[-1].setStretch(0, 3)
            MerchBoxH1LayoutList[-1].setStretch(1, 8)
            MerchBoxH1LayoutList[-1].setStretch(2, 2)
            MerchBoxList[-1].setLayout(MerchBoxH1LayoutList[-1])
            self.mainVerticalLayer.addWidget(MerchBoxList[-1])
        self.merchList.setWidget(self.scrollAreaWidgetContents)
        self.horizontalLayout_3.addWidget(self.merchList)
        
    def clExit(self):
        app.quit()
예제 #16
0
class HttpClient(QObject):
    sig_ended = pyqtSignal(bool)

    def __init__(self):
        super().__init__()
        self.network_manager = QNetworkAccessManager()
        self.request = QNetworkRequest()
        self.request.setRawHeader(b"accept", b"application/json")
        self.request.setRawHeader(
            b'user-agent',
            b'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, '
            b'like Gecko) Chrome/66.0.3359.139 Safari/537.36')

        self._ended = True
        self._reply = None
        self._text = b''
        self._string = ''
        self._status_code = None
        self._json = None
        self._headers = None

        self._connect_to_slot()

    def reply(self):
        return self._reply

    def json(self):
        return self._json

    def status(self):
        return self._status_code

    def text(self):
        return self._string

    def headers(self):
        return self._headers

    def content_type(self):
        content_type = self._headers['content-type']
        if 'text/html' in content_type:
            return 'html'
        elif 'test/plain' in content_type:
            return 'text'
        elif 'application/json' in content_type:
            return 'json'

    def _save_header(self, raw_headers):
        h = {}
        for t in raw_headers:
            h.update({str.lower(bytes(t[0]).decode()): bytes(t[1]).decode()})

        self._headers = h

    def set_header(self, header):
        """
        header must consist of strings of dict

        :param header: dict
        """
        if isinstance(header, dict):
            for k in header:
                self.request.setRawHeader(k.encode(), header[k].encode())

    def get(self, url: str, header=None):
        """
        Get http request

        :param url:
        :param header:
        """
        self.request.setUrl(QUrl(url))
        self.set_header(header)
        self.network_manager.get(self.request)

    def post(self, url: str, header: list(tuple()) = None, data: bytes = None):
        self.request.setUrl(QUrl(url))
        self.set_header(header)
        self.network_manager.post(self.request, data)

    def put(self, url: str, header: list(tuple()) = None, data: bytes = None):
        self.request.setUrl(QUrl(url))
        self.set_header(header)
        self.network_manager.put(self.request, data)

    def delete(self, url: str, header: list(tuple()) = None):
        self.request.setUrl(QUrl(url))
        self.set_header(header)
        self.network_manager.deleteResource(self.request)

    def _connect_to_slot(self):
        self.network_manager.finished.connect(self.slot_reply_finished)

    def slot_reply_finished(self, data: QNetworkReply):

        self._reply = data
        self._text = data.readAll()
        self._string = bytes(self._text).decode()
        self._status_code = data.attribute(
            QNetworkRequest.HttpStatusCodeAttribute)
        self._save_header(data.rawHeaderPairs())

        if self.content_type() == 'json':
            if len(self._string):
                self._json = json.loads(self._string)
        else:
            self._json = None

        if self._status_code >= 400:
            print(self._string)

        self.sig_ended.emit(True)
        data.deleteLater()
예제 #17
0
파일: daemon.py 프로젝트: hydrargyrum/eye
class Ycm(QObject, CategoryMixin):
	"""YCMD instance control"""

	YCMD_CMD = ['ycmd']
	"""Base ycmd command.

	Useful if ycmd is not in `PATH` or set permanent arguments
	"""

	IDLE_SUICIDE = 120
	"""Maximum time after which ycmd should quit if it has received no requests.

	A periodic ping is sent by `Ycm` objects.
	"""

	CHECK_REPLY_SIGNATURE = True
	TIMEOUT = 10

	def __init__(self, **kwargs):
		super(Ycm, self).__init__(**kwargs)

		self.addr = None
		"""Address of the ycmd server."""

		self.port = 0
		"""TCP port of the ycmd server."""

		self._ready = False
		self.secret = ''
		self.config = {}

		self.proc = QProcess()
		self.proc.started.connect(self.procStarted)
		self.proc.errorOccurred.connect(self.procError)
		self.proc.finished.connect(self.procFinished)

		self.pingTimer = QTimer(self)
		self.pingTimer.timeout.connect(self.ping)

		self.network = QNetworkAccessManager()

		qApp().aboutToQuit.connect(self.stop)

		self.addCategory('ycm_control')

	def makeConfig(self):
		self.secret = generate_key()
		self.config['hmac_secret'] = b64encode(self.secret).decode('ascii')

		fd, path = tempfile.mkstemp()
		with open(path, 'w') as fd:
			fd.write(json.dumps(self.config))
			fd.flush()
		return path

	def checkReply(self, reply):
		"""Check the ycmd reply is a success.

		Checks the `reply` has a HTTP 200 status code and the signature is valid.
		In case of error, raises a :any:`ServerError`.

		:type reply: QNetworkReply
		"""
		reply.content = bytes(reply.readAll())

		if reply.error():
			raise ServerError(reply.error() + 1000, reply.errorString(), reply.content)
		status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
		if status_code != 200:
			data = reply.content.decode('utf-8')
			try:
				data = json.loads(data)
			except (ValueError, JSONDecodeError):
				LOGGER.info('ycmd replied non-json body: %r', data)

			raise ServerError(status_code, data)

		if not self.CHECK_REPLY_SIGNATURE:
			return
		actual = b64decode(bytes(reply.rawHeader(HMAC_HEADER)))
		expected = self._hmacDigest(reply.content)

		if not hmac.compare_digest(expected, actual):
			raise RuntimeError('Server signature did not match')

	def _jsonReply(self, reply):
		body = reply.content.decode('utf-8')
		return json.loads(body)

	def _hmacDigest(self, msg):
		return hmac.new(self.secret, msg, hashlib.sha256).digest()

	def _sign(self, verb, path, body=b''):
		digests = [self._hmacDigest(part) for part in [verb, path, body]]
		return self._hmacDigest(b''.join(digests))

	def _doGet(self, path):
		url = urlunsplit(('http', self.addr, path, '', ''))
		sig = self._sign(b'GET', path.encode('utf-8'), b'')
		headers = {
			HMAC_HEADER: b64encode(sig)
		}

		request = QNetworkRequest(QUrl(url))
		for hname in headers:
			request.setRawHeader(hname, headers[hname])

		reply = self.network.get(request)
		return reply

	def _doPost(self, path, **params):
		url = urlunsplit(('http', self.addr, path, '', ''))
		body = json.dumps(params)
		sig = self._sign(b'POST', path.encode('utf-8'), body.encode('utf-8'))
		headers = {
			HMAC_HEADER: b64encode(sig),
			'Content-Type': 'application/json'
		}

		request = QNetworkRequest(QUrl(url))
		for hname in headers:
			request.setRawHeader(hname, headers[hname])
		reply = self.network.post(request, body)
		return reply

	def ping(self):
		def handleReply():
			self.checkReply(reply)
			if not self._ready:
				self._ready = True
				self.pingTimer.start(60000)
				self.ready.emit()

		reply = self._doGet('/healthy')
		reply.finished.connect(handleReply)
		reply.finished.connect(reply.deleteLater)

	def start(self):
		if not self.port:
			self.port = generate_port()
		self.addr = 'localhost:%s' % self.port
		path = self.makeConfig()

		_, outlogpath = tempfile.mkstemp(prefix='eye-ycm', suffix='.out.log')
		_, errlogpath = tempfile.mkstemp(prefix='eye-ycm', suffix='.err.log')
		LOGGER.info('ycmd will log to %r and %r', outlogpath, errlogpath)

		cmd = (self.YCMD_CMD +
			[
				'--idle_suicide_seconds', str(self.IDLE_SUICIDE),
				'--port', str(self.port),
				'--options_file', path,
				'--stdout', outlogpath,
				'--stderr', errlogpath,
			])

		LOGGER.debug('will run %r', cmd)

		self.proc.start(cmd[0], cmd[1:])

		self._ready = False

	@Slot()
	def stop(self, wait=0.2):
		if self.proc.state() == QProcess.NotRunning:
			return
		self.proc.terminate()
		if self.proc.state() == QProcess.NotRunning:
			return
		time.sleep(wait)
		self.proc.kill()

	def isRunning(self):
		return self.proc.state() == QProcess.Running

	def connectTo(self, addr):
		self.addr = addr
		self._ready = False
		self.pingTimer.start(1000)

	@Slot()
	def procStarted(self):
		LOGGER.debug('daemon has started')
		self.pingTimer.start(1000)

	@Slot(int, QProcess.ExitStatus)
	def procFinished(self, code, status):
		LOGGER.info('daemon has exited with status %r and code %r', status, code)
		self.pingTimer.stop()
		self._ready = False

	@Slot(QProcess.ProcessError)
	def procError(self, error):
		LOGGER.warning('daemon failed to start (%r): %s', error, self.errorString())

	ready = Signal()

	def _commonPostDict(self, filepath, filetype, contents):
		d = {
			'filepath': filepath,
			'filetype': filetype,
			'file_data': {
				filepath: {
					'filetypes': [filetype],
					'contents': contents
				}
			},
			'line_num': 1, # useless but required
			'column_num': 1,
		}
		return d

	def _postSimpleRequest(self, urlpath, filepath, filetype, contents, **kwargs):
		d = self._commonPostDict(filepath, filetype, contents)
		d.update(**kwargs)

		return self._doPost(urlpath, **d)

	def acceptExtraConf(self, filepath, filetype, contents):
		reply = self._postSimpleRequest('/load_extra_conf_file', filepath, filetype, contents)
		reply.finished.connect(reply.deleteLater)

	def rejectExtraConf(self, filepath, filetype, contents):
		reply = self._postSimpleRequest('/ignore_extra_conf_file', filepath, filetype, contents,
		                                _ignore_body=True)
		reply.finished.connect(reply.deleteLater)

	def sendParse(self, filepath, filetype, contents, retry_extra=True):
		d = {
			'event_name': 'FileReadyToParse'
		}
		reply = self._postSimpleRequest('/event_notification', filepath, filetype, contents, **d)

		def handleReply():
			try:
				self.checkReply(reply)
			except ServerError as exc:
				excdata = exc.args[1]
				if (isinstance(excdata, dict) and 'exception' in excdata and
					excdata['exception']['TYPE'] == 'UnknownExtraConf' and
					retry_extra):
					confpath = excdata['exception']['extra_conf_file']
					LOGGER.info('ycmd encountered %r and wonders if it should be loaded', confpath)

					accepted = sendIntent(None, 'queryExtraConf', conf=confpath)
					if accepted:
						LOGGER.info('extra conf %r will be loaded', confpath)
						self.acceptExtraConf(confpath, filetype, contents)
					else:
						LOGGER.info('extra conf %r will be rejected', confpath)
						self.rejectExtraConf(confpath, filetype, contents)

					return self.sendParse(filepath, filetype, contents, retry_extra=False)
				raise

		reply.finished.connect(handleReply)
		reply.finished.connect(reply.deleteLater)

	if 0:
		def querySubcommandsList(self, filepath, filetype, contents, line, col):
			return self._postSimpleRequest('/defined_subcommands', filepath, filetype, contents)

		def _querySubcommand(self, filepath, filetype, contents, line, col, *args):
			d = {
				'command_arguments': list(args)
			}
			return self._postSimpleRequest('/run_completer_command', filepath, filetype, contents, **d)

		def queryGoTo(self, *args):
			res = self._querySubcommand(*args)
			if res.get('filepath'):
				return {
					'filepath': res['filepath'],
					'line': res['line_num'],
					'column': res['column_num'],
				}

		def queryInfo(self, *args):
			res = self._querySubcommand(*args)
			return res.get('message', '') or res.get('detailed_info', '')

	def queryCompletions(self, filepath, filetype, contents, line, col):
		d = {
			'line_num': line,
			'column_num': col,
		}
		return self._postSimpleRequest('/completions', filepath, filetype, contents, **d)

	if 0:
		def queryDiagnostic(self, filepath, filetype, contents, line, col):
			return self._postSimpleRequest('/detailed_diagnostic', filepath, filetype, contents)

		def queryDebug(self, filepath, filetype, contents, line, col):
			return self._postSimpleRequest('/debug_info', filepath, filetype, contents)
예제 #18
0
class SaveManager:
    manager = QNetworkAccessManager()

    def __init__(self, app):
        self.app = app
        self.manager.finished.connect(self.saveServerFinished)

    def save(self, image):
        # save file on disk
        path = QFileDialog().getSaveFileName(self.app, 'Save image', '',
                                             '*.jpg')
        image.save(path[0])

    def saveBuffer(self, image):
        cb: QClipboard = QApplication.clipboard()
        cb.setImage(image, mode=cb.Clipboard)

        QMessageBox.information(self.app, 'Copy', 'Image copied to buffer')

    def saveServer(self, image):
        name = 'tmp.png'
        url = 'https://joxi-server.herokuapp.com/save'

        # save file on disk
        image.save(f"./{name}")

        with open(f"./{name}", 'rb') as img:
            files = {
                'image': (name, img, 'multipart/form-data', {
                    'Expires': '0'
                })
            }

            with requests.Session() as s:
                # create pyqt QNetworkRequest from requests module
                r = s.post(url, files=files)
                request = r.request
                request.prepare(method="POST", url=url)

                request_qt = QNetworkRequest(QUrl(url))

                for header, value in request.headers.items():
                    request_qt.setRawHeader(bytes(header, encoding="utf-8"),
                                            bytes(value, encoding="utf-8"))

                # set handlers
                self.manager = QNetworkAccessManager()
                self.manager.finished.connect(self.saveServerFinished)
                self.manager.post(request_qt, request.body)

    def saveServerFinished(self, reply: QNetworkReply):
        # if error on server occured
        if reply.error():
            QMessageBox.critical(self.app, 'Query error', reply.errorString())
            return

        try:
            data = loads(bytes(reply.readAll()))
            QMessageBox.information(self.app, 'Success query', data['message'])
        except Exception as e:
            QMessageBox.critical(self.app, 'Query error', 'Error in parsing')
            print(e)
            return

        # save path to clipboard
        cb: QClipboard = QApplication.clipboard()
        cb.setText(data['path'], mode=cb.Clipboard)
예제 #19
0
class JSControllerObject(QObject):
    http_request_finished = pyqtSignal(int, int, str)

    def __init__(self, parent):
        super().__init__(parent)
        self.parent = parent
        self.network_manager = QNetworkAccessManager()

    def http_response(self, callback_id, reply):
        if not self.job():
            return

        if reply.error() == 0:
            data_str = reply.readAll().data().decode(encoding='UTF-8')
            self.http_request_finished.emit(callback_id, reply.error(),
                                            data_str)
        else:
            self.http_request_finished.emit(callback_id, reply.error(), '')
        reply.deleteLater()

    def post_finished(self, network_reply):
        error = network_reply.error()
        url = network_reply.url()
        url_str = url.toString()
        if error != 0:
            request = network_reply.request()

            request_headers_string = 'Request:\n'
            for header in request.rawHeaderList():
                request_headers_string += '{}: {}\n'.format(
                    header.data().decode(encoding=HTTP_HEADER_CHARSET),
                    request.rawHeader(header).data().decode(
                        encoding=HTTP_HEADER_CHARSET))

            response_headers_string = 'Response:\n'
            for header in network_reply.rawHeaderList():
                response_headers_string += '{}: {}\n'.format(
                    header.data().decode(encoding=HTTP_HEADER_CHARSET),
                    network_reply.rawHeader(header).data().decode(
                        encoding=HTTP_HEADER_CHARSET))

            logger.error(
                self.prepend_id(
                    'e_id="{eid};{estr}" url="{url}"\n{req_h}\n{res_h}'.format(
                        eid=error,
                        estr=network_reply.errorString(),
                        url=url_str,
                        req_h=request_headers_string,
                        res_h=response_headers_string)))
        else:
            logger.info('Post successful {}'.format(url_str))

    @pyqtSlot(str, str)
    def post_request(self, url, data):
        if not self.job():
            logger.error(
                self.prepend_id(
                    'Invalid State. post_request called when no current job'))
            return

        logger.info(
            self.prepend_id("Posting {} request to {}".format(self.job(),
                                                              url)))
        req = QNetworkRequest(QUrl(url))
        req.setHeader(QNetworkRequest.ContentTypeHeader, 'application/json')
        network_reply = self.network_manager.post(req, data.encode('UTF-8'))
        network_reply.finished.connect(
            lambda: self.post_finished(network_reply))

    @pyqtSlot(QVariant)
    def log_message(self, message):
        logger.info(self.prepend_id('js: {}'.format(message)))

    @pyqtSlot(QVariant)
    def log_error(self, message):
        logger.error(self.prepend_id('js: {}'.format(message)))

    @pyqtProperty(QVariant)
    def job_dict(self):
        return self.parent.current_job.dict()

    def job(self):
        return self.parent.current_job

    @pyqtSlot(int, str)
    def http_request(self, callback_id, url):
        if not self.parent.current_job:
            logger.error(
                self.prepend_id(
                    'Invalid State. http_request called when no current job'))
            return

        qnetwork_reply = self.network_manager.get(QNetworkRequest(QUrl(url)))
        qnetwork_reply.finished.connect(
            lambda: self.http_response(callback_id, qnetwork_reply))

    @pyqtProperty(str)
    def current_state(self):
        if self.parent.current_job.state:
            return self.parent.current_job.state
        else:
            return 'main'

    @pyqtSlot()
    def done(self):
        if not self.parent.current_job:
            logger.error(
                self.prepend_id(
                    'Invalid State. done called when no current job'))
            return

        logger.info(self.prepend_id('Done Job {}'.format(self.job())))

        self.parent.reset()
        self.parent.job_finished.emit()

    @pyqtSlot()
    def abort(self, retry_after_sec=60):
        if not self.parent.current_job:
            logger.error(
                self.prepend_id(
                    'Invalid State. abort called when no current job'))
            return

        logger.error(self.prepend_id('Job aborting {}'.format(self.job())))
        retry_job = self.job().get_retry_job()
        if retry_job.retry <= MAX_RETRIES:
            logger.info(
                self.prepend_id('Retrying :{} after {}sec'.format(
                    retry_job, retry_after_sec)))
            QTimer.singleShot(
                retry_after_sec * 1000, Qt.VeryCoarseTimer,
                lambda: self.parent.new_job_received.emit(retry_job))
        else:
            logger.error(
                self.prepend_id('Max Retries reached:{}'.format(retry_job)))

        self.parent.reset()
        self.parent.job_finished.emit()

    def prepend_id(self, message):
        return '[{}] {}'.format(self.parent.id, message)

    @pyqtSlot(QVariant)
    def load(self, job_dict):
        if not self.parent.current_job:
            logger.error(
                self.prepend_id(
                    'Invalid State. load called when no current job'))
            return

        self.parent.new_job_received.emit(
            self.parent.current_job.new_state(**job_dict))
class D3DCloudPrintOutputDevice(OutputDevice):
    def __init__(self):
        super().__init__("d3dcloudprint")

        self.setPriority(1)
        self.setName("Doodle3D WiFi-Box")
        self.setShortDescription(
            i18n_catalog.i18nc("@action:button",
                               "Print with Doodle3D WiFi-Box"))
        self.setDescription(
            i18n_catalog.i18nc("@properties:tooltip",
                               "Print with Doodle3D WiFi-Box"))
        self.setIconName("print")

        self._progress_message = None

        self.base_url = "http://connect.doodle3d.com"

        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onFinished)

        self.uploading = False

    def requestWrite(self,
                     nodes,
                     file_name=None,
                     filter_by_machine=False,
                     file_handler=None,
                     **kwargs):
        if not self.uploading:
            self.startUpload()

    def startUpload(self):
        Logger.log("d", "Upload to Doodle3D connect started")
        self.uploading = True

        self._progress_message = Message(
            i18n_catalog.i18nc("@info:status",
                               "Connecting to Doodle3D Connect"), 0, False, -1)
        self._progress_message.addAction(
            "Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
        self._progress_message.actionTriggered.connect(
            self._onMessageActionTriggered)
        self._progress_message.show()
        # Request upload credentials
        url = QUrl("https://gcodeserver.doodle3d.com/upload")
        self._post_reply = self._manager.post(QNetworkRequest(url),
                                              QByteArray())
        self._post_reply.error.connect(self._onNetworkError)

    def uploadGCode(self, data):
        try:
            job_name = Application.getInstance().getPrintInformation(
            ).jobName.strip()
            if job_name == "":
                job_name = "untitled_print"

            global_stack = Application.getInstance().getGlobalContainerStack()
            machine_manager = Application.getInstance().getMachineManager()

            cura_printer_type = machine_manager.activeDefinitionId
            printer_type = ConnectPrinterIdTranslation.curaPrinterIdToConnect(
                cura_printer_type)
            # Fall back to marlin or makerbot generic if printer is not supported on WiFi-Box
            if printer_type is None:
                gcode_flavor = global_stack.getProperty(
                    "machine_gcode_flavor", "value")
                if gcode_flavor == "RepRap (Marlin/Sprinter)":
                    printer_type = "marlin_generic"
                elif gcode_flavor == "MakerBot":
                    printer_type = "makerbot_generic"
                else:
                    printer_type = cura_printer_type

            sliceInfo = {
                'printer': {
                    'type': printer_type,
                    'title': global_stack.getName()
                },
                'material': {
                    'type': global_stack.material.getId(),
                    'title': global_stack.material.getName()
                },
                'filamentThickness':
                global_stack.getProperty("material_diameter", "value"),
                'temperature':
                global_stack.getProperty("material_print_temperature",
                                         "value"),
                'name':
                job_name
            }

            gcode_list = getattr(
                Application.getInstance().getController().getScene(),
                "gcode_list")
            gcode = ";%s\n" % json.dumps(sliceInfo)
            for line in gcode_list:
                gcode += line

            multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)

            for prop_name, prop_value in data["data"]["reservation"][
                    "fields"].items():
                part = QHttpPart()
                part.setHeader(QNetworkRequest.ContentDispositionHeader,
                               "form-data; name=\"%s\"" % prop_name)
                part.setBody(prop_value.encode())
                multi_part.append(part)

            gcode_part = QHttpPart()
            gcode_part.setHeader(QNetworkRequest.ContentDispositionHeader,
                                 "form-data; name=\"file\"")
            gcode_part.setBody(gcode.encode())
            multi_part.append(gcode_part)

            upload_url = QUrl(data["data"]["reservation"]["url"])
            Logger.log("d", "{}", upload_url)

            self._post_reply = self._manager.post(QNetworkRequest(upload_url),
                                                  multi_part)
            self._post_reply.uploadProgress.connect(self._onProgress)
            self._post_reply.error.connect(self._onNetworkError)
            self._progress_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Sending data to Doodle3D Connect"), 0,
                False, -1)
            self._progress_message.addAction(
                "Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), None,
                "")
            self._progress_message.actionTriggered.connect(
                self._onMessageActionTriggered)
            self._progress_message.show()
            multi_part.setParent(self._post_reply)
        except Exception as e:
            self._progress_message.hide()
            self._progress_message = Message(
                i18n_catalog.i18nc(
                    "@info:status",
                    "Unable to send data to Doodle3D Connect. Is another job still active?"
                ))
            self._progress_message.show()
            Logger.log(
                "e", "An exception occured during G-code upload: %s" % str(e))

    def _onProgress(self, bytes_sent, bytes_total):
        if bytes_total > 0:
            # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
            # timeout responses if this happens.
            progress = bytes_sent / bytes_total * 100
            if progress < 100:
                if progress > self._progress_message.getProgress():
                    self._progress_message.setProgress(progress)
            else:
                self._progress_message.hide()
                self._progress_message = Message(
                    i18n_catalog.i18nc("@info:status",
                                       "Storing data on Doodle3D Connect"), 0,
                    False, -1)
                self._progress_message.show()
        else:
            self._progress_message.setProgress(0)

    def _onFinished(self, reply):
        if reply.error() == QNetworkReply.TimeoutError:
            Logger.log("w",
                       "Received a timeout on a request to the G-code server")
            if self._post_reply:
                self._post_reply.abort()
                self._post_reply.uploadProgress.disconnect(self._onProgress)
                self._post_reply = None
            self._progress_message.hide()
            return

        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)

        if not status_code:
            Logger.log("d", "A reply from %s did not have status code.",
                       reply.url().toString())
            self.uploading = False
            self._progress_message.hide()
            return

        reply_url = reply.url().toString()
        if "upload" in reply_url:
            try:
                json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
            except json.decoder.JSONDecodeError:
                Logger.log(
                    "w",
                    "Received invalid upload credentials request reply from Connect: Not valid JSON."
                )
                return

            self.gcodeId = json_data["data"]["id"]

            self._progress_message.hide()
            self.uploadGCode(json_data)
        elif "amazonaws" in reply_url:
            if status_code in [200, 201, 202, 204]:
                self._progress_message.hide()
                self._progress_message = Message(
                    i18n_catalog.i18nc("@info:status",
                                       "File sent to Doodle3D Connect"), 0)
                self._progress_message.addAction(
                    "open_browser",
                    i18n_catalog.i18nc("@action:button", "Open Connect.."),
                    "globe",
                    i18n_catalog.i18nc(
                        "@info:tooltip",
                        "Open the Doodle3D Connect web interface"))
                self._progress_message.actionTriggered.connect(
                    self._onMessageActionTriggered)
                self._progress_message.show()
                self.uploading = False
                Logger.log("d", "uploaded to amazon")
            else:
                self._progress_message.hide()
                Logger.log("w", "Unexpected status code in reply from AWS S3")

        if reply == self._post_reply:
            self._post_reply = None

    def _onMessageActionTriggered(self, message, action):
        if action == "open_browser":
            QDesktopServices.openUrl(
                QUrl("%s?uuid=%s" % (self.base_url, self.gcodeId)))
        elif action == "Cancel":
            Logger.log("d", "canceled upload to Doodle3D Connect")
            self._progress_message.hide()
            if self._post_reply:
                self._post_reply.abort()
                self._post_reply = None
        else:
            Logger.log("d", "unknown action: %s" % action)

    def _onNetworkError(self, error):
        Logger.log(
            "w",
            "Network error: %s, %s" % (error, self._post_reply.errorString()))

    def _onSslError(reply, errors):
        for error in errors:
            Logger.log("w", "%s" % error)
class DiscoverOctoPrintAction(MachineAction):
    def __init__(self, parent: QObject = None) -> None:
        super().__init__("DiscoverOctoPrintAction", catalog.i18nc("@action", "Connect OctoPrint"))

        self._qml_url = "DiscoverOctoPrintAction.qml"

        self._application = CuraApplication.getInstance()
        self._network_plugin = None # type: Optional[OctoPrintOutputDevicePlugin]

        #   QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
        #   hook itself into the event loop, which results in events never being fired / done.
        self._network_manager = QNetworkAccessManager()
        self._network_manager.finished.connect(self._onRequestFinished)

        self._settings_reply = None # type: Optional[QNetworkReply]

        self._appkeys_supported = False
        self._appkey_reply = None # type: Optional[QNetworkReply]
        self._appkey_request = None # type: Optional[QNetworkRequest]
        self._appkey_instance_id = ""

        self._appkey_poll_timer = QTimer()
        self._appkey_poll_timer.setInterval(500)
        self._appkey_poll_timer.setSingleShot(True)
        self._appkey_poll_timer.timeout.connect(self._pollApiKey)

        # Try to get version information from plugin.json
        plugin_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "plugin.json")
        try:
            with open(plugin_file_path) as plugin_file:
                plugin_info = json.load(plugin_file)
                self._plugin_version = plugin_info["version"]
        except:
            # The actual version info is not critical to have so we can continue
            self._plugin_version = "0.0"
            Logger.logException("w", "Could not get version information for the plugin")

        self._user_agent = ("%s/%s %s/%s" % (
            self._application.getApplicationName(),
            self._application.getVersion(),
            "OctoPrintPlugin",
            self._plugin_version
        )).encode()

        self._instance_responded = False
        self._instance_in_error = False
        self._instance_api_key_accepted = False
        self._instance_supports_sd = False
        self._instance_supports_camera = False

        # Load keys cache from preferences
        self._preferences = self._application.getPreferences()
        self._preferences.addPreference("octoprint/keys_cache", "")

        try:
            self._keys_cache = json.loads(self._deobfuscateString(self._preferences.getValue("octoprint/keys_cache")))
        except ValueError:
            self._keys_cache = {} # type: Dict[str, Any]
        if not isinstance(self._keys_cache, dict):
            self._keys_cache = {} # type: Dict[str, Any]

        self._additional_components = None # type:Optional[QObject]

        ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
        self._application.engineCreatedSignal.connect(self._createAdditionalComponentsView)

    @pyqtProperty(str, constant=True)
    def pluginVersion(self) -> str:
        return self._plugin_version

    @pyqtSlot()
    def startDiscovery(self) -> None:
        if not self._network_plugin:
            self._network_plugin = self._application.getOutputDeviceManager().getOutputDevicePlugin(self._plugin_id)
            if not self._network_plugin:
                return
            self._network_plugin.addInstanceSignal.connect(self._onInstanceDiscovery)
            self._network_plugin.removeInstanceSignal.connect(self._onInstanceDiscovery)
            self._network_plugin.instanceListChanged.connect(self._onInstanceDiscovery)
            self.instancesChanged.emit()
        else:
            # Restart bonjour discovery
            self._network_plugin.startDiscovery()

    def _onInstanceDiscovery(self, *args) -> None:
        self.instancesChanged.emit()

    @pyqtSlot(str)
    def removeManualInstance(self, name: str) -> None:
        if not self._network_plugin:
            return

        self._network_plugin.removeManualInstance(name)

    @pyqtSlot(str, str, int, str, bool, str, str)
    def setManualInstance(self, name: str, address: str, port: int, path: str, useHttps: bool, userName: str = "", password: str = "") -> None:
        if not self._network_plugin:
            return

        # This manual printer could replace a current manual printer
        self._network_plugin.removeManualInstance(name)

        self._network_plugin.addManualInstance(name, address, port, path, useHttps, userName, password)

    def _onContainerAdded(self, container: "ContainerInterface") -> None:
        # Add this action as a supported action to all machine definitions
        if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
            self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey())

    instancesChanged = pyqtSignal()
    appKeysSupportedChanged = pyqtSignal()
    appKeyReceived = pyqtSignal()

    @pyqtProperty("QVariantList", notify = instancesChanged)
    def discoveredInstances(self) -> List[Any]:
        if self._network_plugin:
            instances = list(self._network_plugin.getInstances().values())
            instances.sort(key = lambda k: k.name)
            return instances
        else:
            return []

    @pyqtSlot(str)
    def setInstanceId(self, key: str) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if global_container_stack:
            global_container_stack.setMetaDataEntry("octoprint_id", key)

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

    @pyqtSlot(result = str)
    def getInstanceId(self) -> str:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return ""

        return global_container_stack.getMetaDataEntry("octoprint_id", "")

    @pyqtSlot(str, str, str, str)
    def requestApiKey(self, instance_id: str, base_url: str, basic_auth_username: str = "", basic_auth_password: str = "") -> None:
        ## Request appkey
        self._appkey_instance_id = instance_id
        self._appkey_request = self._createRequest(QUrl(base_url + "plugin/appkeys/request"), basic_auth_username, basic_auth_password)
        self._appkey_request.setRawHeader(b"Content-Type", b"application/json")
        data = json.dumps({"app": "Cura"})
        self._appkey_reply = self._network_manager.post(self._appkey_request, data.encode())

    @pyqtSlot()
    def cancelApiKeyRequest(self) -> None:
        if self._appkey_reply:
            self._appkey_reply.abort()
            self._appkey_reply = None

        self._appkey_request = None # type: Optional[QNetworkRequest]

        self._appkey_poll_timer.stop()

    def _pollApiKey(self) -> None:
        if not self._appkey_request:
            return
        self._appkey_reply = self._network_manager.get(self._appkey_request)

    @pyqtSlot(str, str, str)
    def probeAppKeySupport(self, base_url: str, basic_auth_username: str = "", basic_auth_password: str = "") -> None:
        self._appkeys_supported = False
        self.appKeysSupportedChanged.emit()

        appkey_probe_request = self._createRequest(QUrl(base_url + "plugin/appkeys/probe"), basic_auth_username, basic_auth_password)
        self._appkey_request = self._network_manager.get(appkey_probe_request)

    @pyqtSlot(str, str, str, str)
    def testApiKey(self, base_url: str, api_key: str, basic_auth_username: str = "", basic_auth_password: str = "") -> None:
        self._instance_responded = False
        self._instance_api_key_accepted = False
        self._instance_supports_sd = False
        self._instance_supports_camera = False
        self.selectedInstanceSettingsChanged.emit()

        if api_key != "":
            Logger.log("d", "Trying to access OctoPrint instance at %s with the provided API key." % base_url)

            ## Request 'settings' dump
            settings_request = self._createRequest(QUrl(base_url + "api/settings"), basic_auth_username, basic_auth_password)
            settings_request.setRawHeader("X-Api-Key".encode(), api_key.encode())
            self._settings_reply = self._network_manager.get(settings_request)
        else:
            if self._settings_reply:
                self._settings_reply.abort()
                self._settings_reply = None

    @pyqtSlot(str)
    def setApiKey(self, api_key: str) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return

        global_container_stack.setMetaDataEntry("octoprint_api_key", base64.b64encode(api_key.encode("ascii")).decode("ascii"))

        self._keys_cache[self.getInstanceId()] = api_key
        keys_cache = base64.b64encode(json.dumps(self._keys_cache).encode("ascii")).decode("ascii")
        self._preferences.setValue("octoprint/keys_cache", keys_cache)

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

    ##  Get the stored API key of an instance, or the one stored in the machine instance
    #   \return key String containing the key of the machine.
    @pyqtSlot(str, result=str)
    def getApiKey(self, instance_id: str) -> str:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return ""

        if instance_id == self.getInstanceId():
            api_key = self._deobfuscateString(global_container_stack.getMetaDataEntry("octoprint_api_key", ""))
        else:
            api_key = self._keys_cache.get(instance_id, "")

        return api_key

    selectedInstanceSettingsChanged = pyqtSignal()

    @pyqtProperty(bool, notify = selectedInstanceSettingsChanged)
    def instanceResponded(self) -> bool:
        return self._instance_responded

    @pyqtProperty(bool, notify = selectedInstanceSettingsChanged)
    def instanceInError(self) -> bool:
        return self._instance_in_error

    @pyqtProperty(bool, notify = selectedInstanceSettingsChanged)
    def instanceApiKeyAccepted(self) -> bool:
        return self._instance_api_key_accepted

    @pyqtProperty(bool, notify = selectedInstanceSettingsChanged)
    def instanceSupportsSd(self) -> bool:
        return self._instance_supports_sd

    @pyqtProperty(bool, notify = selectedInstanceSettingsChanged)
    def instanceSupportsCamera(self) -> bool:
        return self._instance_supports_camera

    @pyqtProperty(bool, notify = appKeysSupportedChanged)
    def instanceSupportsAppKeys(self) -> bool:
        return self._appkeys_supported

    @pyqtSlot(str, str, str)
    def setContainerMetaDataEntry(self, container_id: str, key: str, value: str) -> None:
        containers = ContainerRegistry.getInstance().findContainers(id = container_id)
        if not containers:
            Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id)
            return

        containers[0].setMetaDataEntry(key, value)

    @pyqtSlot(bool)
    def applyGcodeFlavorFix(self, apply_fix: bool) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return

        gcode_flavor = "RepRap (Marlin/Sprinter)" if apply_fix else "UltiGCode"
        if global_container_stack.getProperty("machine_gcode_flavor", "value") == gcode_flavor:
            # No need to add a definition_changes container if the setting is not going to be changed
            return

        # Make sure there is a definition_changes container to store the machine settings
        definition_changes_container = global_container_stack.definitionChanges
        if definition_changes_container == ContainerRegistry.getInstance().getEmptyInstanceContainer():
            definition_changes_container = CuraStackBuilder.createDefinitionChangesContainer(
                global_container_stack, global_container_stack.getId() + "_settings")

        definition_changes_container.setProperty("machine_gcode_flavor", "value", gcode_flavor)

        # Update the has_materials metadata flag after switching gcode flavor
        definition = global_container_stack.getBottom()
        if not definition or definition.getProperty("machine_gcode_flavor", "value") != "UltiGCode" or definition.getMetaDataEntry("has_materials", False):
            # In other words: only continue for the UM2 (extended), but not for the UM2+
            return

        has_materials = global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"

        material_container = global_container_stack.material

        if has_materials:
            global_container_stack.setMetaDataEntry("has_materials", True)

            # Set the material container to a sane default
            if material_container == ContainerRegistry.getInstance().getEmptyInstanceContainer():
                search_criteria = { "type": "material", "definition": "fdmprinter", "id": global_container_stack.getMetaDataEntry("preferred_material")}
                materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
                if materials:
                    global_container_stack.material = materials[0]
        else:
            # The metadata entry is stored in an ini, and ini files are parsed as strings only.
            # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
            if "has_materials" in global_container_stack.getMetaData():
                global_container_stack.removeMetaDataEntry("has_materials")

            global_container_stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer()

        self._application.globalContainerStackChanged.emit()


    @pyqtSlot(str)
    def openWebPage(self, url: str) -> None:
        QDesktopServices.openUrl(QUrl(url))

    def _createAdditionalComponentsView(self) -> None:
        Logger.log("d", "Creating additional ui components for OctoPrint-connected printers.")

        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "OctoPrintComponents.qml")
        self._additional_components = self._application.createQmlComponent(path, {"manager": self})
        if not self._additional_components:
            Logger.log("w", "Could not create additional components for OctoPrint-connected printers.")
            return

        self._application.addAdditionalComponent("monitorButtons", self._additional_components.findChild(QObject, "openOctoPrintButton"))

    ##  Handler for all requests that have finished.
    def _onRequestFinished(self, reply: QNetworkReply) -> None:

        http_status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        if not http_status_code:
            # Received no or empty reply
            return

        if reply.operation() == QNetworkAccessManager.PostOperation:
            if "/plugin/appkeys/request" in reply.url().toString():  # Initial AppKey request
                if http_status_code == 201 or http_status_code == 202:
                    Logger.log("w", "Start polling for AppKeys decision")
                    if not self._appkey_request:
                        return
                    self._appkey_request.setUrl(reply.header(QNetworkRequest.LocationHeader))
                    self._appkey_request.setRawHeader(b"Content-Type", b"")
                    self._appkey_poll_timer.start()
                elif http_status_code == 404:
                    Logger.log("w", "This instance of OctoPrint does not support AppKeys")
                    self._appkey_request = None # type: Optional[QNetworkRequest]
                else:
                    response = bytes(reply.readAll()).decode()
                    Logger.log("w", "Unknown response when requesting an AppKey: %d. OctoPrint said %s" % (http_status_code, response))
                    self._appkey_request = None # type: Optional[QNetworkRequest]

        if reply.operation() == QNetworkAccessManager.GetOperation:
            if "/plugin/appkeys/probe" in reply.url().toString():  # Probe for AppKey support
                if http_status_code == 204:
                    self._appkeys_supported = True
                else:
                    self._appkeys_supported = False
                self.appKeysSupportedChanged.emit()
            if "/plugin/appkeys/request" in reply.url().toString():  # Periodic AppKey request poll
                if http_status_code == 202:
                    self._appkey_poll_timer.start()
                elif http_status_code == 200:
                    Logger.log("d", "AppKey granted")
                    self._appkey_request = None # type: Optional[QNetworkRequest]
                    try:
                        json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log("w", "Received invalid JSON from octoprint instance.")
                        return

                    self._keys_cache[self._appkey_instance_id] = json_data["api_key"]
                    self.appKeyReceived.emit()
                elif http_status_code == 404:
                    Logger.log("d", "AppKey denied")
                    self._appkey_request = None # type: Optional[QNetworkRequest]
                else:
                    response = bytes(reply.readAll()).decode()
                    Logger.log("w", "Unknown response when waiting for an AppKey: %d. OctoPrint said %s" % (http_status_code, response))
                    self._appkey_request = None # type: Optional[QNetworkRequest]

            if "api/settings" in reply.url().toString():  # OctoPrint settings dump from /settings:
                self._instance_in_error = False

                if http_status_code == 200:
                    Logger.log("d", "API key accepted by OctoPrint.")
                    self._instance_api_key_accepted = True

                    try:
                        json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log("w", "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if "feature" in json_data and "sdSupport" in json_data["feature"]:
                        self._instance_supports_sd = json_data["feature"]["sdSupport"]

                    if "webcam" in json_data and "streamUrl" in json_data["webcam"]:
                        stream_url = json_data["webcam"]["streamUrl"]
                        if stream_url: #not empty string or None
                            self._instance_supports_camera = True

                elif http_status_code == 401:
                    Logger.log("d", "Invalid API key for OctoPrint.")
                    self._instance_api_key_accepted = False

                elif http_status_code == 502 or http_status_code == 503:
                    Logger.log("d", "OctoPrint is not running.")
                    self._instance_api_key_accepted = False
                    self._instance_in_error = True

                self._instance_responded = True
                self.selectedInstanceSettingsChanged.emit()

    def _createRequest(self, url: str, basic_auth_username: str = "", basic_auth_password: str = "") -> QNetworkRequest:
        request = QNetworkRequest(url)
        request.setRawHeader("User-Agent".encode(), self._user_agent)
        if basic_auth_username and basic_auth_password:
            data = base64.b64encode(("%s:%s" % (basic_auth_username, basic_auth_password)).encode()).decode("utf-8")
            request.setRawHeader("Authorization".encode(), ("Basic %s" % data).encode())
        return request

    ##  Utility handler to base64-decode a string (eg an obfuscated API key), if it has been encoded before
    def _deobfuscateString(self, source: str) -> str:
        try:
            return base64.b64decode(source.encode("ascii")).decode("ascii")
        except UnicodeDecodeError:
            return source
예제 #22
0
class VAbstractNetworkClient(QObject):
    """Позволяет приложению отправлять сетевые запросы и получать на них ответы.

    Вся работа с сетью должна быть инкапсулирована в его наследниках.
    """
    @staticmethod
    def contentTypeFrom(reply: QNetworkReply, default=None):
        """Определяет и возвращает MIME-тип содержимого (со всеми вспомогательными данными, напр., кодировкой)
        из http-заголовка `Content-type` в ответе `reply`.
        Если тип содержимого определить невозможно, возвращает `default`.
        """
        assert reply
        contentType = reply.header(QNetworkRequest.ContentTypeHeader)
        if contentType:
            assert isinstance(contentType, str)  # TODO: Delete me!
            return contentType
        return default

    @staticmethod
    def encodingFrom(reply: QNetworkReply, default: str = "utf-8") -> str:
        """Определяет и возвращает кодировку содержимого из http-заголовка `Content-type` в ответе `reply`.
        Если кодировку определить невозможно, возвращает `default`.
        """
        missing = object()
        contentType = VAbstractNetworkClient.contentTypeFrom(reply, missing)

        if contentType is missing:
            return default

        try:
            charset = contentType.split(";")[1]
            assert "charset" in charset
            encoding = charset.split("=")[1]
            return encoding.strip()
        except:
            return default

    @staticmethod
    def waitForFinished(reply: QNetworkReply, timeout: int = -1):
        """Блокирует вызывающий метод на время, пока не будет завершен сетевой ответ `reply` (то есть пока
        не испустится сигнал `reply.finished`), или пока не истечет `timeout` миллисекунд.
        Если `timeout` меньше 0 (по умолчанию), то по данному таймеру блокировка отменяться не будет.
        """
        if reply.isFinished():
            return

        event_loop = QEventLoop()
        reply.finished.connect(event_loop.quit)
        if timeout >= 0:
            timer = QTimer()
            timer.setInterval(timeout)
            timer.setSingleShot(True)
            timer.timeout.connect(event_loop.quit)
            # Если блокировка отменится до истечения таймера, то при выходе из метода таймер остановится и уничтожится.
            timer.start()
        event_loop.exec()
        reply.finished.disconnect(event_loop.quit)

    networkAccessManagerChanged = pyqtSignal(QNetworkAccessManager,
                                             arguments=['manager'])
    """Сигнал об изменении менеджера доступа к сети.

    :param QNetworkAccessManager manager: Новый менеджер доступа к сети.
    """

    baseUrlChanged = pyqtSignal(QUrl, arguments=['url'])
    """Сигнал об изменении базового url-а.

    :param QUrl url: Новый базовый url.
    """

    replyFinished = pyqtSignal(QNetworkReply, arguments=['reply'])
    """Сигнал о завершении ответа на сетевой запрос.

    :param QNetworkReply reply: Завершенный сетевой запрос.
    """

    def __init__(self, parent: QObject = None):
        super().__init__(parent)

        self.__networkAccessManager = QNetworkAccessManager(parent=self)
        self.__baseUrl = QUrl()

    def getNetworkAccessManager(self) -> QNetworkAccessManager:
        """Возвращает менеджер доступа к сети."""
        return self.__networkAccessManager

    def setNetworkAccessManager(self, manager: QNetworkAccessManager):
        """Устанавливает менеджер доступа к сети."""
        assert manager
        if manager is self.__networkAccessManager:
            return
        if self.__networkAccessManager.parent() is self:
            self.__networkAccessManager.deleteLater()
        self.__networkAccessManager = manager
        self.networkAccessManagerChanged.emit(manager)

    networkAccessManager = pyqtProperty(type=QNetworkAccessManager,
                                        fget=getNetworkAccessManager,
                                        fset=setNetworkAccessManager,
                                        notify=networkAccessManagerChanged,
                                        doc="Менеджер доступа к сети.")

    def getBaseUrl(self) -> QUrl:
        """Возвращает базовый url."""
        return QUrl(self.__baseUrl)

    def setBaseUrl(self, url: QUrl):
        """Устанавливает базовый url."""
        if url == self.__baseUrl:
            return
        self.__baseUrl = QUrl(url)
        self.baseUrlChanged.emit(url)

    baseUrl = pyqtProperty(type=QUrl,
                           fget=getBaseUrl,
                           fset=setBaseUrl,
                           notify=baseUrlChanged,
                           doc="Базовый url.")

    def _connectReplySignals(self, reply: QNetworkReply):
        """Соединяет сигналы ответа с сигналами клиента."""
        reply.finished.connect(lambda: self.replyFinished.emit(reply))
        # TODO: Добавить сюда подключение остальных сигналов.

    def _get(self, request: QNetworkRequest) -> QNetworkReply:
        """Запускает отправку GET-запроса и возвращает ответ :class:`QNetworkReply` на него."""
        reply = self.__networkAccessManager.get(request)
        self._connectReplySignals(reply)
        return reply

    def _head(self, request: QNetworkRequest) -> QNetworkReply:
        """Запускает отправку HEAD-запроса и возвращает ответ :class:`QNetworkReply` на него."""
        reply = self.__networkAccessManager.head(request)
        self._connectReplySignals(reply)
        return reply

    def _post(self, request: QNetworkRequest, data=None) -> QNetworkReply:
        """Запускает отправку POST-запроса и возвращает ответ :class:`QNetworkReply` на него.

        _post(self, request: QNetworkRequest) -> QNetworkReply.
        _post(self, request: QNetworkRequest, data: bytes) -> QNetworkReply.
        _post(self, request: QNetworkRequest, data: bytearray) -> QNetworkReply.
        _post(self, request: QNetworkRequest, data: QByteArray) -> QNetworkReply.
        _post(self, request: QNetworkRequest, data: QIODevice) -> QNetworkReply.
        _post(self, request: QNetworkRequest, data: QHttpMultiPart) -> QNetworkReply.
        """
        if data is not None:
            reply = self.__networkAccessManager.post(request, data)
        else:
            reply = self.__networkAccessManager.sendCustomRequest(
                request, b"POST")
        self._connectReplySignals(reply)
        return reply

    def _put(self, request: QNetworkRequest, data=None) -> QNetworkReply:
        """Запускает отправку PUT-запроса и возвращает ответ :class:`QNetworkReply` на него.

        _put(self, request: QNetworkRequest) -> QNetworkReply.
        _put(self, request: QNetworkRequest, data: bytes) -> QNetworkReply.
        _put(self, request: QNetworkRequest, data: bytearray) -> QNetworkReply.
        _put(self, request: QNetworkRequest, data: QByteArray) -> QNetworkReply.
        _put(self, request: QNetworkRequest, data: QIODevice) -> QNetworkReply.
        _put(self, request: QNetworkRequest, data: QHttpMultiPart) -> QNetworkReply.
        """
        if data is not None:
            reply = self.__networkAccessManager.put(request, data)
        else:
            reply = self.__networkAccessManager.sendCustomRequest(
                request, b"PUT")
        self._connectReplySignals(reply)
        return reply

    def _delete(self, request: QNetworkRequest, data=None) -> QNetworkReply:
        """Запускает отправку DELETE-запроса и возвращает ответ :class:`QNetworkReply` на него.

        _delete(self, request: QNetworkRequest) -> QNetworkReply.
        _delete(self, request: QNetworkRequest, data: bytes) -> QNetworkReply.
        _delete(self, request: QNetworkRequest, data: bytearray) -> QNetworkReply.
        _delete(self, request: QNetworkRequest, data: QByteArray) -> QNetworkReply.
        _delete(self, request: QNetworkRequest, data: QIODevice) -> QNetworkReply.
        _delete(self, request: QNetworkRequest, data: QHttpMultiPart) -> QNetworkReply.
        """
        if data is not None:
            reply = self.__networkAccessManager.deleteResource(request)
        else:
            reply = self.__networkAccessManager.sendCustomRequest(
                request, b"DELETE")
        self._connectReplySignals(reply)
        return reply

    def _sendCustomRequest(self,
                           request: QNetworkRequest,
                           verb: bytes,
                           data=None) -> QNetworkReply:
        """Запускает отправку пользовательского запроса и возвращает ответ :class:`QNetworkReply` на него.

        _sendCustomRequest(self, request: QNetworkRequest, verb: bytes) -> QNetworkReply.
        _sendCustomRequest(self, request: QNetworkRequest, verb: bytes, data: bytes) -> QNetworkReply.
        _sendCustomRequest(self, request: QNetworkRequest, verb: bytes, data: bytearray) -> QNetworkReply.
        _sendCustomRequest(self, request: QNetworkRequest, verb: bytes, data: QByteArray) -> QNetworkReply.
        _sendCustomRequest(self, request: QNetworkRequest, verb: bytes, data: QIODevice) -> QNetworkReply.
        _sendCustomRequest(self, request: QNetworkRequest, verb: bytes, data: QHttpMultiPart) -> QNetworkReply.
        """
        reply = self.__networkAccessManager.sendCustomRequest(
            request, verb, data)
        self._connectReplySignals(reply)
        return reply
class Node(QObject):
    def __init__(self, node_id: str, parent=None):
        QObject.__init__(self, parent)
        self._temperature = 293
        self._node_id = node_id
        self._server_url = "localhost"
        self._access_card = ""
        self.updateServerUrl(self._server_url)

        self._all_chart_data = {}

        self._network_manager = QNetworkAccessManager()
        self._network_manager.finished.connect(self._onNetworkFinished)
        self._data = None
        self._enabled = True
        self._incoming_connections = []
        self._outgoing_connections = []
        self._onFinishedCallbacks = {}  # type: Dict[QNetworkReply, Callable[[QNetworkReply], None]]
        self._description = ""
        self._static_properties = {}
        self._performance = 1
        self._target_performance = 1
        self._min_performance = 0.5
        self._max_performance = 1
        self._max_safe_temperature = 500
        self._heat_convection = 1.0
        self._heat_emissivity = 1.0
        self._modifiers = []
        self._active = True

        self._update_timer = QTimer()
        self._update_timer.setInterval(30000)
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self.partialUpdate)


        # Timer that is used when the server could not be reached.
        self._failed_update_timer = QTimer()
        self._failed_update_timer.setInterval(30000)
        self._failed_update_timer.setSingleShot(True)
        self._failed_update_timer.timeout.connect(self.fullUpdate)

        self._additional_properties = []
        self._converted_additional_properties = {}
        self.server_reachable = False
        self._optimal_temperature = 200
        self._is_temperature_dependant = False
        self._resources_required = []
        self._optional_resources_required = []
        self._resources_received = []
        self._resources_produced = []
        self._resources_provided = []
        self._health = 100

        self._max_amount_stored = 0
        self._amount_stored = 0
        self._effectiveness_factor = 0

        self._random_delay_timer = QTimer()
        self._random_delay_timer.setInterval(random.randint(0, 29000))
        self._random_delay_timer.setSingleShot(True)
        self._random_delay_timer.timeout.connect(self.fullUpdate)
        self._random_delay_timer.start()

    temperatureChanged = Signal()
    historyPropertiesChanged = Signal()
    historyDataChanged = Signal()
    enabledChanged = Signal()
    incomingConnectionsChanged = Signal()
    outgoingConnectionsChanged = Signal()
    performanceChanged = Signal()
    staticPropertiesChanged = Signal()
    modifiersChanged = Signal()
    additionalPropertiesChanged = Signal()
    minPerformanceChanged = Signal()
    maxPerformanceChanged = Signal()
    maxSafeTemperatureChanged = Signal()
    heatConvectionChanged = Signal()
    heatEmissivityChanged = Signal()
    serverReachableChanged = Signal(bool)
    isTemperatureDependantChanged = Signal()
    optimalTemperatureChanged = Signal()
    targetPerformanceChanged = Signal()
    resourcesRequiredChanged = Signal()
    optionalResourcesRequiredChanged = Signal()
    resourcesReceivedChanged = Signal()
    resourcesProducedChanged = Signal()
    resourcesProvidedChanged = Signal()
    healthChanged = Signal()
    maxAmountStoredChanged = Signal()
    amountStoredChanged = Signal()
    effectivenessFactorChanged = Signal()
    activeChanged = Signal()

    def setAccessCard(self, access_card):
        self._access_card = access_card
        self._updateUrlsWithAuth(self._server_url, access_card)

    def _updateUrlsWithAuth(self, server_url, access_card):
        self._performance_url = f"{self._server_url}/node/{self._node_id}/performance/?accessCardID={self._access_card}"

    def updateServerUrl(self, server_url):
        if server_url == "":
            return

        self._server_url = f"http://{server_url}:5000"

        self._source_url = f"{self._server_url}/node/{self._node_id}/"
        self._incoming_connections_url = f"{self._server_url}/node/{self._node_id}/connections/incoming/"
        self._all_chart_data_url = f"{self._server_url}/node/{self._node_id}/all_property_chart_data/?showLast=50"
        self._outgoing_connections_url = f"{self._server_url}/node/{self._node_id}/connections/outgoing/"
        self._static_properties_url = f"{self._server_url}/node/{self._node_id}/static_properties/"
        self._modifiers_url = f"{self._server_url}/node/{self._node_id}/modifiers/"
        self._updateUrlsWithAuth(self._server_url, self._access_card)

    def get(self, url: str, callback: Callable[[QNetworkReply], None]) -> None:
        reply = self._network_manager.get(QNetworkRequest(QUrl(url)))
        self._onFinishedCallbacks[reply] = callback

    def fullUpdate(self) -> None:
        """
        Request all data of this node from the server
        :return:
        """
        self.get(self._incoming_connections_url, self._onIncomingConnectionsFinished)
        self.get(self._outgoing_connections_url, self._onOutgoingConnectionsFinished)
        self.get(self._static_properties_url, self._onStaticPropertiesFinished)

        self.partialUpdate()
        self._update_timer.start()

    @Slot()
    def partialUpdate(self) -> None:
        """
        Request all the data that is dynamic
        :return:
        """
        self.get(self._source_url, self._onSourceUrlFinished)
        self.get(self._all_chart_data_url, self._onChartDataFinished)
        self.get(self._modifiers_url, self._onModifiersChanged)

    def _setServerReachable(self, server_reachable: bool):
        if self.server_reachable != server_reachable:
            self.server_reachable = server_reachable
            self.serverReachableChanged.emit(self.server_reachable)

    def _readData(self, reply: QNetworkReply):
        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        if status_code == 404:
            print("Node was not found!")
            return
        # For some magical reason, it segfaults if I convert the readAll() data directly to bytes.
        # So, yes, the extra .data() is needed.
        data = bytes(reply.readAll().data())
        if not data or status_code == 503:
            self._failed_update_timer.start()
            self._update_timer.stop()
            self._setServerReachable(False)
            return None
        self._setServerReachable(True)
        try:
            return json.loads(data)
        except json.decoder.JSONDecodeError:
            return None

    def updateAdditionalProperties(self, data):
        if self._additional_properties != data:
            self._additional_properties = data
            self._converted_additional_properties = {}
            # Clear the list and convert them in a way that we can use them in a repeater.
            for additional_property in data:
                self._converted_additional_properties[additional_property["key"]] = {
                                                              "value": additional_property["value"],
                                                              "max_value": additional_property["max_value"]}

            self.additionalPropertiesChanged.emit()

    def _onModifiersChanged(self, reply: QNetworkReply):
        result = self._readData(reply)
        if result is None:
            result = []
        if self._modifiers != result:
            self._modifiers = result
            self.modifiersChanged.emit()

    def _onPerformanceChanged(self, reply: QNetworkReply):
        print("CALLBAAACk")
        result = self._readData(reply)
        if not result:
            return
        if self._performance != result:
            self._performance = result
            self.performanceChanged.emit()

    @Slot(float)
    def setPerformance(self, performance):
        data = "{\"performance\": %s}" % performance
        self._target_performance = performance
        self.targetPerformanceChanged.emit()
        reply = self._network_manager.put(QNetworkRequest(QUrl(self._performance_url)), data.encode())
        self._onFinishedCallbacks[reply] = self._onPerformanceChanged

    @Slot(str)
    def addModifier(self, modifier: str):
        data = "{\"modifier_name\": \"%s\"}" % modifier
        request = QNetworkRequest(QUrl(self._modifiers_url))
        request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")

        reply = self._network_manager.post(request, data.encode())

        self._onFinishedCallbacks[reply] = self._onModifiersChanged

    @Property(float, notify=performanceChanged)
    def performance(self):
        return self._performance

    @Property(float, notify=targetPerformanceChanged)
    def targetPerformance(self):
        return self._target_performance

    @Property("QVariantList", notify=resourcesRequiredChanged)
    def resourcesRequired(self):
        return self._resources_required

    @Property("QVariantList", notify=resourcesProducedChanged)
    def resourcesProduced(self):
        return self._resources_produced

    @Property("QVariantList", notify=resourcesProvidedChanged)
    def resourcesProvided(self):
        return self._resources_provided

    @Property("QVariantList", notify=resourcesReceivedChanged)
    def resourcesReceived(self):
        return self._resources_received

    @Property("QVariantList", notify=optionalResourcesRequiredChanged)
    def optionalResourcesRequired(self):
        return self._optional_resources_required

    @Property("QVariantList", notify=modifiersChanged)
    def modifiers(self):
        return self._modifiers

    @Property(float, notify=minPerformanceChanged)
    def min_performance(self):
        return self._min_performance

    @Property(float, notify=maxPerformanceChanged)
    def max_performance(self):
        return self._max_performance

    @Property(float, notify=healthChanged)
    def health(self):
        return self._health

    def _onStaticPropertiesFinished(self, reply: QNetworkReply) -> None:
        result = self._readData(reply)
        if not result:
            return
        if self._static_properties != result:
            self._static_properties = result
            self.staticPropertiesChanged.emit()

    def _onIncomingConnectionsFinished(self, reply: QNetworkReply):
        result = self._readData(reply)
        if not result:
            return
        self._incoming_connections = result
        self.incomingConnectionsChanged.emit()

    def _onOutgoingConnectionsFinished(self, reply: QNetworkReply):
        result = self._readData(reply)
        if not result:
            return
        self._outgoing_connections = result
        self.outgoingConnectionsChanged.emit()

    @Property("QVariantList", notify=incomingConnectionsChanged)
    def incomingConnections(self):
        return self._incoming_connections

    @Property(str, notify=staticPropertiesChanged)
    def description(self):
        return self._static_properties.get("description", "")

    @Property(str, notify=staticPropertiesChanged)
    def node_type(self):
        return self._static_properties.get("node_type", "")

    @Property(str, notify=staticPropertiesChanged)
    def custom_description(self):
        return self._static_properties.get("custom_description", "")

    @Property("QStringList", notify=staticPropertiesChanged)
    def supported_modifiers(self):
        return self._static_properties.get("supported_modifiers", "")

    @Property(bool, notify=staticPropertiesChanged)
    def hasSettablePerformance(self):
        return self._static_properties.get("has_settable_performance", False)

    @Property(str, notify=staticPropertiesChanged)
    def label(self):
        return self._static_properties.get("label", self._node_id)

    @Property(float, notify=staticPropertiesChanged)
    def surface_area(self):
        return self._static_properties.get("surface_area", 0)

    @Property(float, notify=isTemperatureDependantChanged)
    def isTemperatureDependant(self):
        return self._is_temperature_dependant

    @Property(float, notify=activeChanged)
    def active(self):
        return self._active

    @Property(float, notify=optimalTemperatureChanged)
    def optimalTemperature(self):
        return self._optimal_temperature

    @Property(float, notify=maxSafeTemperatureChanged)
    def max_safe_temperature(self):
        return self._max_safe_temperature

    @Property(float, notify=heatConvectionChanged)
    def heat_convection(self):
        return self._heat_convection

    @Property(float, notify=heatEmissivityChanged)
    def heat_emissivity(self):
        return self._heat_emissivity

    @Property("QVariantList", notify=outgoingConnectionsChanged)
    def outgoingConnections(self):
        return self._outgoing_connections

    def _onSourceUrlFinished(self, reply: QNetworkReply):
        data = self._readData(reply)
        if not data:
            return
        self._updateProperty("temperature", data["temperature"] - 273.15 )
        self._updateProperty("enabled", bool(data["enabled"]))
        self._updateProperty("active", bool(data["active"]))
        self._updateProperty("performance", data["performance"])
        self._updateProperty("min_performance", data["min_performance"])
        self._updateProperty("max_performance", data["max_performance"])
        self._updateProperty("max_safe_temperature", data["max_safe_temperature"] - 273.15)
        self._updateProperty("heat_convection", data["heat_convection"])
        self._updateProperty("heat_emissivity", data["heat_emissivity"])
        self._updateProperty("is_temperature_dependant", data["is_temperature_dependant"])
        self._updateProperty("optimal_temperature", data["optimal_temperature"] - 273.15)
        self._updateProperty("target_performance", data["target_performance"])
        self._updateProperty("health", data["health"])
        self._updateProperty("effectiveness_factor", data["effectiveness_factor"])

        # We need to update the resources a bit different to prevent recreation of QML items.
        # As such we use tiny QObjects with their own getters and setters.
        # If an object is already in the list with the right type, don't recreate it (just update it's value)
        self.updateResourceList("optional_resources_required", data["optional_resources_required"])
        self.updateResourceList("resources_received", data["resources_received"])
        self.updateResourceList("resources_required", data["resources_required"])
        self.updateResourceList("resources_produced", data["resources_produced"])
        self.updateResourceList("resources_provided", data["resources_provided"])

        self.updateAdditionalProperties(data["additional_properties"])

    def updateResourceList(self, property_name, data):
        list_to_check = getattr(self, "_" + property_name)
        list_updated = False

        for item in data:
            item_found = False
            for resource in list_to_check:
                if item["resource_type"] == resource.type:
                    item_found = True
                    resource.value = item["value"]
                    break

            if not item_found:
                list_updated = True
                list_to_check.append(NodeResource(item["resource_type"], item["value"]))

        if list_updated:
            signal_name = "".join(x.capitalize() for x in property_name.split("_"))
            signal_name = signal_name[0].lower() + signal_name[1:] + "Changed"
            getattr(self, signal_name).emit()

    def _updateProperty(self, property_name, property_value):
        if getattr(self, "_" + property_name) != property_value:
            setattr(self, "_" + property_name, property_value)
            signal_name = "".join(x.capitalize() for x in property_name.split("_"))
            signal_name = signal_name[0].lower() + signal_name[1:] + "Changed"
            getattr(self, signal_name).emit()

    def _onPutUpdateFinished(self, reply: QNetworkReply):
        pass

    def _onChartDataFinished(self, reply: QNetworkReply):
        data = self._readData(reply)
        if not data:
            return

        # Offset is given in the reply, but it's not a list of data. Remove it here.
        if "offset" in data:
            del data["offset"]

        all_keys = set(data.keys())

        keys_changed = False
        data_changed = False
        if set(self._all_chart_data.keys()) != all_keys:
            keys_changed = True
        if self._all_chart_data != data:
            data_changed = True
            self._all_chart_data = data

        if data_changed:
            self.historyDataChanged.emit()
        if keys_changed:
            self.historyPropertiesChanged.emit()

    def _onNetworkFinished(self, reply: QNetworkReply):
        if reply in self._onFinishedCallbacks:
            self._onFinishedCallbacks[reply](reply)
            del self._onFinishedCallbacks[reply]
        else:
            print("GOT A RESPONSE WITH NO CALLBACK!", reply.readAll())

    @Property(str, constant=True)
    def id(self):
        return self._node_id

    @Property(bool, notify = enabledChanged)
    def enabled(self):
        return self._enabled

    @Property(float, notify=amountStoredChanged)
    def amount_stored(self):
        return self._amount_stored

    @Property(float, notify=effectivenessFactorChanged)
    def effectiveness_factor(self):
        return self._effectiveness_factor

    @Property(float, notify=temperatureChanged)
    def temperature(self):
        return self._temperature

    @Property("QVariantList", notify=historyPropertiesChanged)
    def allHistoryProperties(self):
        return list(self._all_chart_data.keys())

    @Property("QVariantMap", notify=historyDataChanged)
    def historyData(self):
        return self._all_chart_data

    @Property("QVariantMap", notify=additionalPropertiesChanged)
    def additionalProperties(self):
        return self._converted_additional_properties

    @Slot()
    def toggleEnabled(self):
        url = self._source_url + "enabled/"
        reply = self._network_manager.put(QNetworkRequest(url), QByteArray())
        self._onFinishedCallbacks[reply] = self._onPutUpdateFinished
        # Already trigger an update, so the interface feels snappy
        self._enabled = not self._enabled
        self.enabledChanged.emit()
예제 #24
0
class E5XmlRpcClient(QObject):
    """
    Class implementing a xmlrpc client for Qt.
    """
    def __init__(self, url, parent=None):
        """
        Constructor
        
        @param url xmlrpc handler URL (string or QUrl)
        @param parent parent object (QObject)
        """
        super(E5XmlRpcClient, self).__init__(parent)
        
        # attributes for the network objects
        self.__networkManager = QNetworkAccessManager(self)
        self.__networkManager.proxyAuthenticationRequired.connect(
            proxyAuthenticationRequired)
        self.__networkManager.finished.connect(self.__replyFinished)
        if SSL_AVAILABLE:
            self.__sslErrorHandler = E5SslErrorHandler(self)
            self.__networkManager.sslErrors.connect(self.__sslErrors)
        
        self.__callmap = {}
        
        self.__request = QNetworkRequest(QUrl(url))
        self.__request.setRawHeader(b"User-Agent", b"E5XmlRpcClient/1.0")
        self.__request.setHeader(QNetworkRequest.ContentTypeHeader, "text/xml")
    
    def setUrl(self, url):
        """
        Public slot to set the xmlrpc handler URL.
        
        @param url xmlrpc handler URL (string or QUrl)
        """
        url = QUrl(url)
        if url.isValid():
            self.__request.setUrl(url)
    
    def call(self, method, args, responseCallback, errorCallback):
        """
        Public method to call the remote server.
        
        @param method name of the remote method to be called (string)
        @param args tuple of method arguments (tuple)
        @param responseCallback method to be called with the returned
            result as a tuple (function)
        @param errorCallback method to be called in case of an error
            with error code and error string (function)
        """
        assert isinstance(args, tuple), \
            "argument must be tuple or Fault instance"
        
        data = xmlrpc.dumps(args, method).encode("utf-8")
        reply = self.__networkManager.post(
            self.__request, QByteArray(data))
        self.__callmap[reply] = (responseCallback, errorCallback)
    
    def abort(self):
        """
        Public method to abort all calls.
        """
        for reply in list(self.__callmap):
            if reply.isRunning():
                reply.abort()
    
    def __sslErrors(self, reply, errors):
        """
        Private slot to handle SSL errors.
        
        @param reply reference to the reply object (QNetworkReply)
        @param errors list of SSL errors (list of QSslError)
        """
        ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0]
        if ignored == E5SslErrorHandler.NotIgnored and reply in self.__callmap:
            self.__callmap[reply][1](xmlrpc.TRANSPORT_ERROR, self.tr(
                "SSL Error"))
    
    def __replyFinished(self, reply):
        """
        Private slot handling the receipt of a reply.
        
        @param reply reference to the finished reply (QNetworkReply)
        """
        if reply not in self.__callmap:
            return
        
        if reply.error() != QNetworkReply.NoError:
            self.__callmap[reply][1](xmlrpc.TRANSPORT_ERROR,
                                     reply.errorString())
        else:
            data = bytes(reply.readAll()).decode("utf-8")
            try:
                data = xmlrpc.loads(data)[0]
                self.__callmap[reply][0](data)
            except xmlrpc.Fault as fault:
                self.__callmap[reply][1](fault.faultCode, fault.faultString)
        
        reply.deleteLater()
        del self.__callmap[reply]
예제 #25
0
class MKSOutputDevice(NetworkedPrinterOutputDevice):
    def __init__(self, instance_id: str, address: str, properties: dict,
                 **kwargs) -> None:
        super().__init__(device_id=instance_id,
                         address=address,
                         properties=properties,
                         **kwargs)
        self._address = address
        self._port = 8080
        self._key = instance_id
        self._properties = properties

        self._target_bed_temperature = 0
        self._num_extruders = 1
        self._hotend_temperatures = [0] * self._num_extruders
        self._target_hotend_temperatures = [0] * self._num_extruders

        self._monitor_view_qml_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "MonitorItem4x.qml")
        # self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")

        self.setPriority(
            3
        )  # Make sure the output device gets selected above local file output and Octoprint XD
        self._active_machine = CuraApplication.getInstance().getMachineManager(
        ).activeMachine
        self.setName(instance_id)
        self.setShortDescription(
            i18n_catalog.i18nc("@action:button", "Print over TFT"))
        self.setDescription(
            i18n_catalog.i18nc("@properties:tooltip", "Print over TFT"))
        self.setIconName("print")
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connected to TFT on {0}").format(self._key))
        Application.getInstance().globalContainerStackChanged.connect(
            self._onGlobalContainerChanged)

        self._socket = None
        self._gl = None
        self._command_queue = Queue()
        self._isPrinting = False
        self._isPause = False
        self._isSending = False
        self._gcode = None
        self._isConnect = False
        self._printing_filename = ""
        self._printing_progress = 0
        self._printing_time = 0
        self._start_time = 0
        self._pause_time = 0
        self.last_update_time = 0
        self.angle = 10
        self._connection_state_before_timeout = None
        self._sdFileList = False
        self.sdFiles = []
        self._mdialog = None
        self._mfilename = None
        self._uploadpath = ''

        self._settings_reply = None
        self._printer_reply = None
        self._job_reply = None
        self._command_reply = None
        self._screenShot = None

        self._image_reply = None
        self._stream_buffer = b""
        self._stream_buffer_start_index = -1

        self._post_reply = None
        self._post_multi_part = None
        self._post_part = None
        self._last_file_name = None
        self._last_file_path = None

        self._progress_message = None
        self._error_message = None
        self._connection_message = None
        self.__additional_components_view = None

        self._update_timer = QTimer()
        self._update_timer.setInterval(
            2000)  # TODO; Add preference for update interval
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)

        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onRequestFinished)

        self._preheat_timer = QTimer()
        self._preheat_timer.setSingleShot(True)
        self._preheat_timer.timeout.connect(self.cancelPreheatBed)
        self._exception_message = None
        self._output_controller = GenericOutputController(self)
        self._number_of_extruders = 1
        self._camera_url = ""
        # Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
        CuraApplication.getInstance().getCuraSceneController(
        ).activeBuildPlateChanged.connect(self.CreateMKSController)

    def _onOutputDevicesChanged(self):
        Logger.log("d", "MKS _onOutputDevicesChanged")

    def connect(self):
        if self._socket is not None:
            self._socket.close()
        self._socket = QTcpSocket()
        self._socket.connectToHost(self._address, self._port)
        global_container_stack = CuraApplication.getInstance(
        ).getGlobalContainerStack()
        self.setShortDescription(
            i18n_catalog.i18nc(
                "@action:button",
                "Print over " + global_container_stack.getName()))
        self.setDescription(
            i18n_catalog.i18nc(
                "@properties:tooltip",
                "Print over " + global_container_stack.getName()))
        Logger.log("d", "MKS socket connecting ")
        # self._socket.waitForConnected(2000)
        self.setConnectionState(
            cast(ConnectionState, UnifiedConnectionState.Connecting))
        self._setAcceptsCommands(True)
        self._socket.readyRead.connect(self.on_read)
        self._update_timer.start()

    def getProperties(self):
        return self._properties

    @pyqtSlot(str, result=str)
    def getProperty(self, key):
        key = key.encode("utf-8")
        if key in self._properties:
            return self._properties.get(key, b"").decode("utf-8")
        else:
            return ""

    @pyqtSlot(result=str)
    def getKey(self):
        return self._key

    @pyqtProperty(str, constant=True)
    def address(self):
        return self._properties.get(b"address", b"").decode("utf-8")

    @pyqtProperty(str, constant=True)
    def name(self):
        return self._properties.get(b"name", b"").decode("utf-8")

    @pyqtProperty(str, constant=True)
    def firmwareVersion(self):
        return self._properties.get(b"firmware_version", b"").decode("utf-8")

    @pyqtProperty(str, constant=True)
    def ipAddress(self):
        return self._address

    @pyqtSlot(float, float)
    def preheatBed(self, temperature, duration):
        self._setTargetBedTemperature(temperature)
        if duration > 0:
            self._preheat_timer.setInterval(duration * 1000)
            self._preheat_timer.start()
        else:
            self._preheat_timer.stop()

    @pyqtSlot()
    def cancelPreheatBed(self):
        self._setTargetBedTemperature(0)
        self._preheat_timer.stop()

    @pyqtSlot()
    def printtest(self):
        self.sendCommand("M104 S0\r\n M140 S0\r\n M106 S255")

    @pyqtSlot()
    def printer_state(self):
        if len(self._printers) <= 0:
            return "offline"
        return self.printers[0].state

    @pyqtSlot()
    def selectfile(self):
        if self._last_file_name:
            return True
        else:
            return False

    @pyqtSlot(str)
    def deleteSDFiles(self, filename):
        # filename = "几何图.gcode"
        self._sendCommand("M30 1:/" + filename)
        self.sdFiles.remove(filename)
        self._sendCommand("M20")

    @pyqtSlot(str)
    def printSDFiles(self, filename):
        self._sendCommand("M23 " + filename)
        self._sendCommand("M24")

    @pyqtSlot()
    def selectFileToUplload(self):
        preferences = Application.getInstance().getPreferences()
        preferences.addPreference("mkswifi/autoprint", "True")
        preferences.addPreference("mkswifi/savepath", "")
        filename, _ = QFileDialog.getOpenFileName(
            None, "choose file", preferences.getValue("mkswifi/savepath"),
            "Gcode(*.gcode;*.g;*.goc)")
        preferences.setValue("mkswifi/savepath", filename)
        self._uploadpath = filename
        if ".g" in filename.lower():
            # Logger.log("d", "selectfile:"+filename)
            if filename in self.sdFiles:
                if self._mdialog:
                    self._mdialog.close()
                self._mdialog = QDialog()
                self._mdialog.setWindowTitle("The " +
                                             filename[filename.rfind("/") +
                                                      1:] +
                                             " file already exists.")
                dialogvbox = QVBoxLayout()
                dialoghbox = QHBoxLayout()
                yesbtn = QPushButton("yes")
                nobtn = QPushButton("no")
                yesbtn.clicked.connect(lambda: self.renameupload(filename))
                nobtn.clicked.connect(self.closeMDialog)
                content = QLabel(
                    "The " + filename[filename.rfind("/") + 1:] +
                    " file already exists. Do you want to rename and upload it?"
                )
                self._mfilename = QLineEdit()
                self._mfilename.setText(filename[filename.rfind("/") + 1:])
                dialoghbox.addWidget(yesbtn)
                dialoghbox.addWidget(nobtn)
                dialogvbox.addWidget(content)
                dialogvbox.addWidget(self._mfilename)
                dialogvbox.addLayout(dialoghbox)
                self._mdialog.setLayout(dialogvbox)
                self._mdialog.exec_()
                return
            if len(filename[filename.rfind("/") + 1:]) >= 30:
                if self._mdialog:
                    self._mdialog.close()
                self._mdialog = QDialog()
                self._mdialog.setWindowTitle(
                    "File name is too long to upload, please rename it.")
                dialogvbox = QVBoxLayout()
                dialoghbox = QHBoxLayout()
                yesbtn = QPushButton("yes")
                nobtn = QPushButton("no")
                yesbtn.clicked.connect(lambda: self.renameupload(filename))
                nobtn.clicked.connect(self.closeMDialog)
                content = QLabel(
                    "File name is too long to upload, please rename it.")
                self._mfilename = QLineEdit()
                self._mfilename.setText(filename[filename.rfind("/") + 1:])
                dialoghbox.addWidget(yesbtn)
                dialoghbox.addWidget(nobtn)
                dialogvbox.addWidget(content)
                dialogvbox.addWidget(self._mfilename)
                dialogvbox.addLayout(dialoghbox)
                self._mdialog.setLayout(dialogvbox)
                self._mdialog.exec_()
                return
            if self.isBusy():
                if self._exception_message:
                    self._exception_message.hide()
                self._exception_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "File cannot be transferred during printing."))
                self._exception_message.show()
                return
            self.uploadfunc(filename)

    def closeMDialog(self):
        if self._mdialog:
            self._mdialog.close()

    def renameupload(self, filename):
        if self._mfilename and ".g" in self._mfilename.text().lower():
            filename = filename[:filename.
                                rfind("/")] + "/" + self._mfilename.text()
            if self._mfilename.text() in self.sdFiles:
                if self._mdialog:
                    self._mdialog.close()
                self._mdialog = QDialog()
                self._mdialog.setWindowTitle("The " +
                                             filename[filename.rfind("/") +
                                                      1:] +
                                             " file already exists.")
                dialogvbox = QVBoxLayout()
                dialoghbox = QHBoxLayout()
                yesbtn = QPushButton("yes")
                nobtn = QPushButton("no")
                yesbtn.clicked.connect(lambda: self.renameupload(filename))
                nobtn.clicked.connect(self.closeMDialog)
                content = QLabel(
                    "The " + filename[filename.rfind("/") + 1:] +
                    " file already exists. Do you want to rename and upload it?"
                )
                self._mfilename = QLineEdit()
                self._mfilename.setText(filename[filename.rfind("/") + 1:])
                dialoghbox.addWidget(yesbtn)
                dialoghbox.addWidget(nobtn)
                dialogvbox.addWidget(content)
                dialogvbox.addWidget(self._mfilename)
                dialogvbox.addLayout(dialoghbox)
                self._mdialog.setLayout(dialogvbox)
                self._mdialog.exec_()
                return
            if len(filename[filename.rfind("/") + 1:]) >= 30:
                if self._mdialog:
                    self._mdialog.close()
                self._mdialog = QDialog()
                self._mdialog.setWindowTitle(
                    "File name is too long to upload, please rename it.")
                dialogvbox = QVBoxLayout()
                dialoghbox = QHBoxLayout()
                yesbtn = QPushButton("yes")
                nobtn = QPushButton("no")
                yesbtn.clicked.connect(lambda: self.renameupload(filename))
                nobtn.clicked.connect(self.closeMDialog)
                content = QLabel(
                    "File name is too long to upload, please rename it.")
                self._mfilename = QLineEdit()
                self._mfilename.setText(filename[filename.rfind("/") + 1:])
                dialoghbox.addWidget(yesbtn)
                dialoghbox.addWidget(nobtn)
                dialogvbox.addWidget(content)
                dialogvbox.addWidget(self._mfilename)
                dialogvbox.addLayout(dialoghbox)
                self._mdialog.setLayout(dialogvbox)
                self._mdialog.exec_()
                return
            if self.isBusy():
                if self._exception_message:
                    self._exception_message.hide()
                self._exception_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "File cannot be transferred during printing."))
                self._exception_message.show()
                return
            self._mdialog.close()
            self.uploadfunc(filename)

    def uploadfunc(self, filename):
        preferences = Application.getInstance().getPreferences()
        preferences.addPreference("mkswifi/autoprint", "True")
        preferences.addPreference("mkswifi/savepath", "")
        self._update_timer.stop()
        self._isSending = True
        self._preheat_timer.stop()
        single_string_file_data = ""
        try:
            f = open(self._uploadpath, "r")
            single_string_file_data = f.read()
            file_name = filename[filename.rfind("/") + 1:]
            self._last_file_name = file_name
            self._progress_message = Message(
                i18n_catalog.i18nc("@info:status", "Sending data to printer"),
                0,
                False,
                -1,
                i18n_catalog.i18nc("@info:title", "Sending Data"),
                option_text=i18n_catalog.i18nc("@label", "Print jobs"),
                option_state=preferences.getValue("mkswifi/autoprint"))
            self._progress_message.addAction(
                "Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), None,
                "")
            self._progress_message.actionTriggered.connect(
                self._cancelSendGcode)
            self._progress_message.optionToggled.connect(
                self._onOptionStateChanged)
            self._progress_message.show()
            self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
            self._post_part = QHttpPart()
            self._post_part.setHeader(
                QNetworkRequest.ContentDispositionHeader,
                "form-data; name=\"file\"; filename=\"%s\"" % file_name)
            self._post_part.setBody(single_string_file_data.encode())
            self._post_multi_part.append(self._post_part)
            post_request = QNetworkRequest(
                QUrl("http://%s/upload?X-Filename=%s" %
                     (self._address, file_name)))
            post_request.setRawHeader(b'Content-Type',
                                      b'application/octet-stream')
            post_request.setRawHeader(b'Connection', b'keep-alive')
            self._post_reply = self._manager.post(post_request,
                                                  self._post_multi_part)
            self._post_reply.uploadProgress.connect(self._onUploadProgress)
            self._post_reply.sslErrors.connect(self._onUploadError)
            self._gcode = None
        except IOError as e:
            Logger.log(
                "e",
                "An exception occurred in network connection: %s" % str(e))
            self._progress_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Send file to printer failed."))
            self._error_message.show()
            self._update_timer.start()
        except Exception as e:
            self._update_timer.start()
            self._progress_message.hide()
            Logger.log(
                "e",
                "An exception occurred in network connection: %s" % str(e))

    @pyqtProperty("QVariantList")
    def getSDFiles(self):
        self._sendCommand("M20")
        return list(self.sdFiles)

    def _setTargetBedTemperature(self, temperature):
        if not self._updateTargetBedTemperature(temperature):
            return
        self._sendCommand(["M140 S%s" % temperature])

    @pyqtSlot(str)
    def sendCommand(self, cmd):
        self._sendCommand(cmd)

    def _sendCommand(self, cmd):
        # Logger.log("d", "_sendCommand %s" % str(cmd))
        if self._socket and self._socket.state() == 2 or self._socket.state(
        ) == 3:
            if isinstance(cmd, str):
                self._command_queue.put(cmd + "\r\n")
            elif isinstance(cmd, list):
                for eachCommand in cmd:
                    self._command_queue.put(eachCommand + "\r\n")

    def disconnect(self):
        # self._updateJobState("")
        self._isConnect = False
        self.setConnectionState(
            cast(ConnectionState, UnifiedConnectionState.Closed))
        if self._socket is not None:
            self._socket.readyRead.disconnect(self.on_read)
            self._socket.close()
        if self._progress_message:
            self._progress_message.hide()
        if self._error_message:
            self._error_message.hide()
        self._update_timer.stop()

    def isConnected(self):
        return self._isConnect

    def isBusy(self):
        return self._isPrinting or self._isPause

    def requestWrite(self,
                     node,
                     file_name=None,
                     filter_by_machine=False,
                     file_handler=None,
                     **kwargs):
        self.writeStarted.emit(self)
        self._update_timer.stop()
        self._isSending = True
        # imagebuff = self._gl.glReadPixels(0, 0, 800, 800, self._gl.GL_RGB,
        #                                   self._gl.GL_UNSIGNED_BYTE)
        active_build_plate = CuraApplication.getInstance(
        ).getMultiBuildPlateModel().activeBuildPlate
        scene = CuraApplication.getInstance().getController().getScene()
        gcode_dict = getattr(scene, "gcode_dict", None)
        if not gcode_dict:
            return
        self._gcode = gcode_dict.get(active_build_plate, None)
        # Logger.log("d", "mks ready for print")
        self.startPrint()

    def startPrint(self):
        global_container_stack = CuraApplication.getInstance(
        ).getGlobalContainerStack()
        if not global_container_stack:
            return
        if self._error_message:
            self._error_message.hide()
            self._error_message = None

        if self._progress_message:
            self._progress_message.hide()
            self._progress_message = None

        if self.isBusy():
            self._error_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Sending data to printer"), 0, False, -1,
                i18n_catalog.i18nc("@info:title", "Sending Data"))
            self._error_message.show()
            return
        job_name = Application.getInstance().getPrintInformation(
        ).jobName.strip()
        if job_name is "":
            job_name = "untitled_print"
            job_name = "cura_file"
        filename = "%s.gcode" % job_name
        if filename in self.sdFiles:
            if self._mdialog:
                self._mdialog.close()
            self._mdialog = QDialog()
            self._mdialog.setWindowTitle("The " +
                                         filename[filename.rfind("/") + 1:] +
                                         " file already exists.")
            dialogvbox = QVBoxLayout()
            dialoghbox = QHBoxLayout()
            yesbtn = QPushButton("yes")
            nobtn = QPushButton("no")
            yesbtn.clicked.connect(self.recheckfilename)
            nobtn.clicked.connect(self.closeMDialog)
            content = QLabel(
                "The " + filename[filename.rfind("/") + 1:] +
                " file already exists. Do you want to rename and upload it?")
            self._mfilename = QLineEdit()
            self._mfilename.setText(filename[filename.rfind("/") + 1:])
            dialoghbox.addWidget(yesbtn)
            dialoghbox.addWidget(nobtn)
            dialogvbox.addWidget(content)
            dialogvbox.addWidget(self._mfilename)
            dialogvbox.addLayout(dialoghbox)
            self._mdialog.setLayout(dialogvbox)
            self._mdialog.exec_()
            return
        if len(filename[filename.rfind("/") + 1:]) >= 30:
            if self._mdialog:
                self._mdialog.close()
            self._mdialog = QDialog()
            self._mdialog.setWindowTitle(
                "File name is too long to upload, please rename it.")
            dialogvbox = QVBoxLayout()
            dialoghbox = QHBoxLayout()
            yesbtn = QPushButton("yes")
            nobtn = QPushButton("no")
            yesbtn.clicked.connect(self.recheckfilename)
            nobtn.clicked.connect(self.closeMDialog)
            content = QLabel(
                "File name is too long to upload, please rename it.")
            self._mfilename = QLineEdit()
            self._mfilename.setText(filename[filename.rfind("/") + 1:])
            dialoghbox.addWidget(yesbtn)
            dialoghbox.addWidget(nobtn)
            dialogvbox.addWidget(content)
            dialogvbox.addWidget(self._mfilename)
            dialogvbox.addLayout(dialoghbox)
            self._mdialog.setLayout(dialogvbox)
            self._mdialog.exec_()
            return
        self._startPrint(filename)

    def recheckfilename(self):
        if self._mfilename and ".g" in self._mfilename.text().lower():
            filename = self._mfilename.text()
            if filename in self.sdFiles:
                if self._mdialog:
                    self._mdialog.close()
                self._mdialog = QDialog()
                self._mdialog.setWindowTitle("The " +
                                             filename[filename.rfind("/") +
                                                      1:] +
                                             " file already exists.")
                dialogvbox = QVBoxLayout()
                dialoghbox = QHBoxLayout()
                yesbtn = QPushButton("yes")
                nobtn = QPushButton("no")
                yesbtn.clicked.connect(self.recheckfilename)
                nobtn.clicked.connect(self.closeMDialog)
                content = QLabel(
                    "The " + filename[filename.rfind("/") + 1:] +
                    " file already exists. Do you want to rename and upload it?"
                )
                self._mfilename = QLineEdit()
                self._mfilename.setText(filename[filename.rfind("/") + 1:])
                dialoghbox.addWidget(yesbtn)
                dialoghbox.addWidget(nobtn)
                dialogvbox.addWidget(content)
                dialogvbox.addWidget(self._mfilename)
                dialogvbox.addLayout(dialoghbox)
                self._mdialog.setLayout(dialogvbox)
                self._mdialog.exec_()
                return
            if len(filename[filename.rfind("/") + 1:]) >= 30:
                if self._mdialog:
                    self._mdialog.close()
                self._mdialog = QDialog()
                self._mdialog.setWindowTitle(
                    "File name is too long to upload, please rename it.")
                dialogvbox = QVBoxLayout()
                dialoghbox = QHBoxLayout()
                yesbtn = QPushButton("yes")
                nobtn = QPushButton("no")
                yesbtn.clicked.connect(self.recheckfilename)
                nobtn.clicked.connect(self.closeMDialog)
                content = QLabel(
                    "File name is too long to upload, please rename it.")
                self._mfilename = QLineEdit()
                self._mfilename.setText(filename[filename.rfind("/") + 1:])
                dialoghbox.addWidget(yesbtn)
                dialoghbox.addWidget(nobtn)
                dialogvbox.addWidget(content)
                dialogvbox.addWidget(self._mfilename)
                dialogvbox.addLayout(dialoghbox)
                self._mdialog.setLayout(dialogvbox)
                self._mdialog.exec_()
                return
            if self.isBusy():
                if self._exception_message:
                    self._exception_message.hide()
                self._exception_message = Message(
                    i18n_catalog.i18nc(
                        "@info:status",
                        "File cannot be transferred during printing."))
                self._exception_message.show()
                return
            self._mdialog.close()
            self._startPrint(filename)

    def _messageBoxCallback(self, button):
        def delayedCallback():
            if button == QMessageBox.Yes:
                self.startPrint()
            else:
                CuraApplication.getInstance().getController().setActiveStage(
                    "PrepareStage")

    def _startPrint(self, file_name="cura_file.gcode"):
        self._preheat_timer.stop()
        self._screenShot = utils.take_screenshot()
        try:
            preferences = Application.getInstance().getPreferences()
            preferences.addPreference("mkswifi/autoprint", "True")
            preferences.addPreference("mkswifi/savepath", "")
            # CuraApplication.getInstance().showPrintMonitor.emit(True)
            self._progress_message = Message(
                i18n_catalog.i18nc("@info:status", "Sending data to printer"),
                0,
                False,
                -1,
                i18n_catalog.i18nc("@info:title", "Sending Data"),
                option_text=i18n_catalog.i18nc("@label", "Print jobs"),
                option_state=preferences.getValue("mkswifi/autoprint"))
            self._progress_message.addAction(
                "Cancel", i18n_catalog.i18nc("@action:button", "Cancel"), None,
                "")
            self._progress_message.actionTriggered.connect(
                self._cancelSendGcode)
            self._progress_message.optionToggled.connect(
                self._onOptionStateChanged)
            self._progress_message.show()
            # job_name = Application.getInstance().getPrintInformation().jobName.strip()
            # if job_name is "":
            #     job_name = "untitled_print"
            # job_name = "cura_file"
            # file_name = "%s.gcode" % job_name
            self._last_file_name = file_name
            Logger.log(
                "d", "mks: " + file_name + Application.getInstance().
                getPrintInformation().jobName.strip())

            single_string_file_data = ""
            if self._screenShot:
                single_string_file_data += utils.add_screenshot(
                    self._screenShot, 50, 50, ";simage:")
                single_string_file_data += utils.add_screenshot(
                    self._screenShot, 200, 200, ";;gimage:")
                single_string_file_data += "\r"
            last_process_events = time.time()
            for line in self._gcode:
                single_string_file_data += line
                if time.time() > last_process_events + 0.05:
                    QCoreApplication.processEvents()
                    last_process_events = time.time()

            self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
            self._post_part = QHttpPart()
            # self._post_part.setHeader(QNetworkRequest.ContentTypeHeader, b'application/octet-stream')
            self._post_part.setHeader(
                QNetworkRequest.ContentDispositionHeader,
                "form-data; name=\"file\"; filename=\"%s\"" % file_name)
            self._post_part.setBody(single_string_file_data.encode())
            self._post_multi_part.append(self._post_part)
            post_request = QNetworkRequest(
                QUrl("http://%s/upload?X-Filename=%s" %
                     (self._address, file_name)))
            post_request.setRawHeader(b'Content-Type',
                                      b'application/octet-stream')
            post_request.setRawHeader(b'Connection', b'keep-alive')
            self._post_reply = self._manager.post(post_request,
                                                  self._post_multi_part)
            self._post_reply.uploadProgress.connect(self._onUploadProgress)
            self._post_reply.sslErrors.connect(self._onUploadError)
            # Logger.log("d", "http://%s:80/upload?X-Filename=%s" % (self._address, file_name))
            self._gcode = None
        except IOError as e:
            Logger.log(
                "e",
                "An exception occurred in network connection: %s" % str(e))
            self._progress_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Send file to printer failed."))
            self._error_message.show()
            self._update_timer.start()
        except Exception as e:
            self._update_timer.start()
            self._progress_message.hide()
            Logger.log(
                "e",
                "An exception occurred in network connection: %s" % str(e))

    def _printFile(self):
        self._sendCommand("M23 " + self._last_file_name)
        self._sendCommand("M24")

    def _onUploadProgress(self, bytes_sent, bytes_total):
        if bytes_total > 0:
            new_progress = bytes_sent / bytes_total * 100
            # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
            # timeout responses if this happens.
            self._last_response_time = time.time()
            if new_progress > self._progress_message.getProgress():
                self._progress_message.show(
                )  # Ensure that the message is visible.
                self._progress_message.setProgress(bytes_sent / bytes_total *
                                                   100)
        else:
            self._progress_message.setProgress(0)
            self._progress_message.hide()

    def _onUploadError(self, reply, sslerror):
        Logger.log("d", "Upload Error")

    def _setHeadPosition(self, x, y, z, speed):
        self._sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))

    def _setHeadX(self, x, speed):
        self._sendCommand("G0 X%s F%s" % (x, speed))

    def _setHeadY(self, y, speed):
        self._sendCommand("G0 Y%s F%s" % (y, speed))

    def _setHeadZ(self, z, speed):
        self._sendCommand("G0 Z%s F%s" % (z, speed))

    def _homeHead(self):
        self._sendCommand("G28 X Y")

    def _homeBed(self):
        self._sendCommand("G28 Z")

    def _moveHead(self, x, y, z, speed):
        self._sendCommand(
            ["G91", "G0 X%s Y%s Z%s F%s" % (x, y, z, speed), "G90"])

    def _update(self):
        if self._socket is not None and (self._socket.state() == 2
                                         or self._socket.state() == 3):
            _send_data = "M105\r\nM997\r\n"
            if self.isBusy():
                _send_data += "M994\r\nM992\r\nM27\r\n"
            while self._command_queue.qsize() > 0:
                _queue_data = self._command_queue.get()
                if "M23" in _queue_data:
                    self._socket.writeData(_queue_data.encode())
                    continue
                if "M24" in _queue_data:
                    self._socket.writeData(_queue_data.encode())
                    continue
                _send_data += _queue_data
            # Logger.log("d", "_send_data: \r\n%s" % _send_data)
            self._socket.writeData(_send_data.encode())
            self._socket.flush()
            # self._socket.waitForReadyRead()
        else:
            Logger.log("d", "MKS wifi reconnecting")
            self.disconnect()
            self.connect()

    def _setJobState(self, job_state):
        if job_state == "abort":
            command = "M26"
        elif job_state == "print":
            if self._isPause:
                command = "M25"
            else:
                command = "M24"
        elif job_state == "pause":
            command = "M25"
        if command:
            self._sendCommand(command)

    @pyqtSlot()
    def cancelPrint(self):
        self._sendCommand("M26")

    @pyqtSlot()
    def pausePrint(self):
        if self.printers[0].state == "paused":
            self._sendCommand("M24")
        else:
            self._sendCommand("M25")

    @pyqtSlot()
    def resumePrint(self):
        self._sendCommand("M25")

    def on_read(self):
        if not self._socket:
            self.disconnect()
            return
        try:
            if not self._isConnect:
                self._isConnect = True
            if self._connection_state != UnifiedConnectionState.Connected:
                self._sendCommand("M20")
                self.setConnectionState(
                    cast(ConnectionState, UnifiedConnectionState.Connected))
                self.setConnectionText(
                    i18n_catalog.i18nc("@info:status", "TFT Connect succeed"))
            # ss = str(self._socket.readLine().data(), encoding=sys.getfilesystemencoding())
            # while self._socket.canReadLine():
            # ss = str(self._socket.readLine().data(), encoding=sys.getfilesystemencoding())
            # ss_list = ss.split("\r\n")
            if not self._printers:
                self._createPrinterList()
            printer = self.printers[0]
            while self._socket.canReadLine():
                s = str(self._socket.readLine().data(),
                        encoding=sys.getfilesystemencoding())
                # Logger.log("d", "mks recv: "+s)
                s = s.replace("\r", "").replace("\n", "")
                # if time.time() - self.last_update_time > 10 or time.time() - self.last_update_time<-10:
                #     Logger.log("d", "mks time:"+str(self.last_update_time)+str(time.time()))
                #     self._sendCommand("M20")
                #     self.last_update_time = time.time()
                if "T" in s and "B" in s and "T0" in s:
                    t0_temp = s[s.find("T0:") + len("T0:"):s.find("T1:")]
                    t1_temp = s[s.find("T1:") + len("T1:"):s.find("@:")]
                    bed_temp = s[s.find("B:") + len("B:"):s.find("T0:")]
                    t0_nowtemp = float(t0_temp[0:t0_temp.find("/")])
                    t0_targettemp = float(t0_temp[t0_temp.find("/") +
                                                  1:len(t0_temp)])
                    t1_nowtemp = float(t1_temp[0:t1_temp.find("/")])
                    t1_targettemp = float(t1_temp[t1_temp.find("/") +
                                                  1:len(t1_temp)])
                    bed_nowtemp = float(bed_temp[0:bed_temp.find("/")])
                    bed_targettemp = float(bed_temp[bed_temp.find("/") +
                                                    1:len(bed_temp)])
                    # cura 3.4 new api
                    printer.updateBedTemperature(bed_nowtemp)
                    printer.updateTargetBedTemperature(bed_targettemp)
                    extruder = printer.extruders[0]
                    extruder.updateTargetHotendTemperature(t0_targettemp)
                    extruder.updateHotendTemperature(t0_nowtemp)
                    # self._number_of_extruders = 1
                    # extruder = printer.extruders[1]
                    # extruder.updateHotendTemperature(t1_nowtemp)
                    # extruder.updateTargetHotendTemperature(t1_targettemp)
                    # only on lower 3.4
                    # self._setBedTemperature(bed_nowtemp)
                    # self._updateTargetBedTemperature(bed_targettemp)
                    # if self._num_extruders > 1:
                    # self._setHotendTemperature(1, t1_nowtemp)
                    # self._updateTargetHotendTemperature(1, t1_targettemp)
                    # self._setHotendTemperature(0, t0_nowtemp)
                    # self._updateTargetHotendTemperature(0, t0_targettemp)
                    continue
                if printer.activePrintJob is None:
                    print_job = PrintJobOutputModel(
                        output_controller=self._output_controller)
                    printer.updateActivePrintJob(print_job)
                else:
                    print_job = printer.activePrintJob
                if s.startswith("M997"):
                    job_state = "offline"
                    if "IDLE" in s:
                        self._isPrinting = False
                        self._isPause = False
                        job_state = 'idle'
                    elif "PRINTING" in s:
                        self._isPrinting = True
                        self._isPause = False
                        job_state = 'printing'
                    elif "PAUSE" in s:
                        self._isPrinting = False
                        self._isPause = True
                        job_state = 'paused'
                    print_job.updateState(job_state)
                    printer.updateState(job_state)
                    # self._updateJobState(job_state)
                    continue
                # print_job.updateState('idle')
                # printer.updateState('idle')
                if s.startswith("M994"):
                    if self.isBusy() and s.rfind("/") != -1:
                        self._printing_filename = s[s.rfind("/") +
                                                    1:s.rfind(";")]
                    else:
                        self._printing_filename = ""
                    print_job.updateName(self._printing_filename)
                    # self.setJobName(self._printing_filename)
                    continue
                if s.startswith("M992"):
                    if self.isBusy():
                        tm = s[s.find("M992") + len("M992"):len(s)].replace(
                            " ", "")
                        mms = tm.split(":")
                        self._printing_time = int(mms[0]) * 3600 + int(
                            mms[1]) * 60 + int(mms[2])
                    else:
                        self._printing_time = 0
                    # Logger.log("d", self._printing_time)
                    print_job.updateTimeElapsed(self._printing_time)
                    # self.setTimeElapsed(self._printing_time)
                    # print_job.updateTimeTotal(self._printing_time)
                    # self.setTimeTotal(self._printing_time)
                    continue
                if s.startswith("M27"):
                    if self.isBusy():
                        self._printing_progress = float(
                            s[s.find("M27") + len("M27"):len(s)].replace(
                                " ", ""))
                        totaltime = self._printing_time / self._printing_progress * 100
                    else:
                        self._printing_progress = 0
                        totaltime = self._printing_time * 100
                    # Logger.log("d", self._printing_time)
                    # Logger.log("d", totaltime)
                    # self.setProgress(self._printing_progress)
                    print_job.updateTimeTotal(self._printing_time)
                    print_job.updateTimeElapsed(self._printing_time * 2 -
                                                totaltime)
                    continue
                if 'Begin file list' in s:
                    self._sdFileList = True
                    self.sdFiles = []
                    self.last_update_time = time.time()
                    continue
                if 'End file list' in s:
                    self._sdFileList = False
                    continue
                if self._sdFileList:
                    s = s.replace("\n", "").replace("\r", "")
                    if s.lower().endswith("gcode") or s.lower().endswith(
                            "gco") or s.lower.endswith("g"):
                        self.sdFiles.append(s)
                    continue
        except Exception as e:
            print(e)

    def _updateTargetBedTemperature(self, temperature):
        if self._target_bed_temperature == temperature:
            return False
        self._target_bed_temperature = temperature
        self.targetBedTemperatureChanged.emit()
        return True

    def _updateTargetHotendTemperature(self, index, temperature):
        if self._target_hotend_temperatures[index] == temperature:
            return False
        self._target_hotend_temperatures[index] = temperature
        self.targetHotendTemperaturesChanged.emit()
        return True

    def _createPrinterList(self):
        printer = PrinterOutputModel(
            output_controller=self._output_controller,
            number_of_extruders=self._number_of_extruders)
        printer.updateName(self.name)
        self._printers = [printer]
        self.printersChanged.emit()

    def _onRequestFinished(self, reply):
        http_status_code = reply.attribute(
            QNetworkRequest.HttpStatusCodeAttribute)
        self._isSending = True
        self._update_timer.start()
        self._sendCommand("M20")
        preferences = Application.getInstance().getPreferences()
        preferences.addPreference("mkswifi/autoprint", "True")
        # preferences.addPreference("mkswifi/savepath", "")
        # preferences.setValue("mkswifi/autoprint", str(self._progress_message.getOptionState()))
        if preferences.getValue("mkswifi/autoprint"):
            self._printFile()
        if not http_status_code:
            return

    def _onOptionStateChanged(self, optstate):
        preferences = Application.getInstance().getPreferences()
        preferences.setValue("mkswifi/autoprint", str(optstate))

    def _cancelSendGcode(self, message_id, action_id):
        self._update_timer.start()
        self._isSending = False
        self._progress_message.hide()
        self._post_reply.abort()

    def CreateMKSController(self):
        Logger.log("d", "Creating additional ui components for mkscontroller.")
        # self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(self._monitor_view_qml_path, {"mkscontroller": self})
        self.__additional_components_view = Application.getInstance(
        ).createQmlComponent(self._monitor_view_qml_path, {"manager": self})
        # trlist = CuraApplication.getInstance()._additional_components
        # for comp in trlist:
        Logger.log("w", "create mkscontroller ")
        if not self.__additional_components_view:
            Logger.log("w", "Could not create ui components for tft35.")
            return

    def _onGlobalContainerChanged(self) -> None:
        self._global_container_stack = Application.getInstance(
        ).getGlobalContainerStack()
        definitions = self._global_container_stack.definition.findDefinitions(
            key="cooling")
        Logger.log("d", definitions[0].label)
예제 #26
0
class HTTPClient(QObject):

    """An HTTP client based on QNetworkAccessManager.

    Intended for APIs, automatically decodes data.

    Attributes:
        _nam: The QNetworkAccessManager used.
        _timers: A {QNetworkReply: QTimer} dict.

    Signals:
        success: Emitted when the operation succeeded.
                 arg: The received data.
        error: Emitted when the request failed.
               arg: The error message, as string.
    """

    success = pyqtSignal(str)
    error = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._nam = QNetworkAccessManager(self)
        self._timers = {}

    def post(self, url, data=None):
        """Create a new POST request.

        Args:
            url: The URL to post to, as QUrl.
            data: A dict of data to send.
        """
        if data is None:
            data = {}
        encoded_data = urllib.parse.urlencode(data).encode('utf-8')
        request = QNetworkRequest(url)
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          'application/x-www-form-urlencoded;charset=utf-8')
        reply = self._nam.post(request, encoded_data)
        self._handle_reply(reply)

    def get(self, url):
        """Create a new GET request.

        Emits success/error when done.

        Args:
            url: The URL to access, as QUrl.
        """
        request = QNetworkRequest(url)
        reply = self._nam.get(request)
        self._handle_reply(reply)

    def _handle_reply(self, reply):
        """Handle a new QNetworkReply."""
        if reply.isFinished():
            self.on_reply_finished(reply)
        else:
            timer = QTimer(self)
            timer.setInterval(10000)
            timer.timeout.connect(reply.abort)
            timer.start()
            self._timers[reply] = timer
            reply.finished.connect(functools.partial(
                self.on_reply_finished, reply))

    def on_reply_finished(self, reply):
        """Read the data and finish when the reply finished.

        Args:
            reply: The QNetworkReply which finished.
        """
        timer = self._timers.pop(reply)
        if timer is not None:
            timer.stop()
            timer.deleteLater()
        if reply.error() != QNetworkReply.NoError:
            self.error.emit(reply.errorString())
            return
        try:
            data = bytes(reply.readAll()).decode('utf-8')
        except UnicodeDecodeError:
            self.error.emit("Invalid UTF-8 data received in reply!")
            return
        self.success.emit(data)
예제 #27
0
class LoginWindow(QWidget):
    tryLogin = pyqtSignal(dict, name="tryLogin")
    loginSuccess = pyqtSignal(name="loginSuccess")

    def __init__(self, state, device):
        super().__init__()

        self.settings = QSettings("Capstone", "posture-of-success")

        self.state = state
        self.device = device
        self.is_waiting = False
        self.login = QNetworkAccessManager()
        self.login.finished.connect(self.login_response)

        self.error_dialog = QErrorMessage()

        # Setup UI
        self.setWindowTitle("성공의 자세")
        self.setWindowIcon(QIcon('icon.png'))
        self.setGeometry(300, 300, 560, 460)

        label = QLabel("로그인")
        label.setProperty("class", "huge")

        self.id_field = QLineEdit(self.settings.value("login/id", ""))
        self.id_field.setPlaceholderText("Email")
        self.pw_field = QLineEdit(self.settings.value("login/pw", ""))
        self.pw_field.setPlaceholderText("Password")
        self.pw_field.setEchoMode(QLineEdit.Password)
        self.pw_field.returnPressed.connect(self.login_clicked)

        fields_box = QVBoxLayout()
        fields_box.addWidget(self.id_field)
        fields_box.addWidget(self.pw_field)

        self.login_button = QPushButton("로그인")
        register_button = QPushButton("회원가입")
        register_button.setProperty("class", "inverted")

        self.save_login_checkbox = QCheckBox("자동 로그인")

        self.device_status = QLabel()

        grid = QGridLayout()
        grid.addWidget(self.save_login_checkbox, 0, 0)
        grid.addWidget(self.login_button, 0, 1)
        grid.addWidget(self.device_status, 1, 0)
        grid.addWidget(register_button, 1, 1)
        grid.setSpacing(20)

        login_box = QVBoxLayout()
        login_box.addStretch(1)
        login_box.addWidget(label)
        login_box.addLayout(fields_box)
        login_box.addLayout(grid)
        login_box.addStretch(1)

        login_box.setContentsMargins(50, 50, 50, 50)
        login_box.setSpacing(20)

        frame = QWidget()
        frame.setLayout(login_box)
        frame.setProperty("class", "frame")

        main_layout = QVBoxLayout()
        main_layout.addWidget(frame)
        main_layout.setContentsMargins(0, 0, 0, 0)

        self.setLayout(main_layout)
        self.setProperty("class", "root")
        self.setContentsMargins(0, 0, 0, 0)
        self.setAttribute(Qt.WA_TranslucentBackground)
        # self.setWindowFlags(Qt.FramelessWindowHint)

        # Connect
        self.update_status()
        self.device.connectedChanged.connect(self.update_status)
        self.login_button.clicked.connect(self.login_clicked)
        register_button.clicked.connect(register)

        if self.settings.value("login/id", "") != "":
            self.save_login_checkbox.setChecked(True)
            self.login_clicked()

    def login_clicked(self):
        if not self.is_waiting:
            print("login clicked")
            self.is_waiting = True
            self.login_button.setEnabled(False)
            data = {
                "email": self.id_field.text(),
                "password": self.pw_field.text()
            }

            req = QNetworkRequest(QUrl(SERVER_BASE_ADDR +
                                       "/api/device/signin"))
            req.setRawHeader("Content-Type".encode('utf-8'),
                             "application/json".encode('utf-8'))
            self.login.post(req, json.dumps(data).encode('utf-8'))

    def login_response(self, response: QNetworkReply):
        self.is_waiting = False
        self.login_button.setEnabled(True)

        err = response.error()
        if err == QNetworkReply.NoError:
            reply = str(response.readAll(), 'utf-8')
            reply_json = json.loads(reply)
            print(reply_json)
            if "success" in reply_json and reply_json["success"]:
                self.state.login(reply_json["email"], reply_json["score"])
                self.state.sensor_values = eval(reply_json["sensor_data"])
                self.loginSuccess.emit()
                self.close()
            else:
                self.error_dialog.showMessage('아이디나 비밀번호가 맞지 않습니다!')
        else:
            self.error_dialog.showMessage("서버 연결에 실패했습니다. 에러 코드=" + str(err))

    def save_login(self, id, pw):
        self.settings.setValue("login/id", id)
        self.settings.setValue("login/pw", pw)
        self.settings.sync()

    def update_status(self):
        if self.device.is_connected():
            self.device_status.setText("장치가 연결되었습니다.")
        else:
            self.device_status.setText("장치 연결을 기다리는 중...")

    def logout(self):
        self.state.logout()
        self.show()

    def closeEvent(self, event: QCloseEvent):
        if self.save_login_checkbox.isChecked():
            print("saving login info")
            self.save_login(self.id_field.text(), self.pw_field.text())
        else:
            print("deleting login info")
            self.save_login("", "")
        if not self.state.is_logged_in():
            QCoreApplication.exit()
예제 #28
0
class CloudApiClient:

    # The cloud URL to use for this remote cluster.
    ROOT_PATH = UltimakerCloudAuthentication.CuraCloudAPIRoot
    CLUSTER_API_ROOT = "{}/connect/v1".format(ROOT_PATH)
    CURA_API_ROOT = "{}/cura/v1".format(ROOT_PATH)

    ## Initializes a new cloud API client.
    #  \param account: The user's account object
    #  \param on_error: The callback to be called whenever we receive errors from the server.
    def __init__(self, account: Account, on_error: Callable[[List[CloudError]],
                                                            None]) -> None:
        super().__init__()
        self._manager = QNetworkAccessManager()
        self._account = account
        self._on_error = on_error
        self._upload = None  # type: Optional[ToolPathUploader]
        # In order to avoid garbage collection we keep the callbacks in this list.
        self._anti_gc_callbacks = []  # type: List[Callable[[], None]]

    ## Gets the account used for the API.
    @property
    def account(self) -> Account:
        return self._account

    ## Retrieves all the clusters for the user that is currently logged in.
    #  \param on_finished: The function to be called after the result is parsed.
    def getClusters(
            self, on_finished: Callable[[List[CloudClusterResponse]],
                                        Any]) -> None:
        url = "{}/clusters".format(self.CLUSTER_API_ROOT)
        reply = self._manager.get(self._createEmptyRequest(url))
        self._addCallback(reply, on_finished, CloudClusterResponse)

    ## Retrieves the status of the given cluster.
    #  \param cluster_id: The ID of the cluster.
    #  \param on_finished: The function to be called after the result is parsed.
    def getClusterStatus(
            self, cluster_id: str, on_finished: Callable[[CloudClusterStatus],
                                                         Any]) -> None:
        url = "{}/clusters/{}/status".format(self.CLUSTER_API_ROOT, cluster_id)
        reply = self._manager.get(self._createEmptyRequest(url))
        self._addCallback(reply, on_finished, CloudClusterStatus)

    ## Requests the cloud to register the upload of a print job mesh.
    #  \param request: The request object.
    #  \param on_finished: The function to be called after the result is parsed.
    def requestUpload(
            self, request: CloudPrintJobUploadRequest,
            on_finished: Callable[[CloudPrintJobResponse], Any]) -> None:
        url = "{}/jobs/upload".format(self.CURA_API_ROOT)
        body = json.dumps({"data": request.toDict()})
        reply = self._manager.put(self._createEmptyRequest(url), body.encode())
        self._addCallback(reply, on_finished, CloudPrintJobResponse)

    ## Uploads a print job tool path to the cloud.
    #  \param print_job: The object received after requesting an upload with `self.requestUpload`.
    #  \param mesh: The tool path data to be uploaded.
    #  \param on_finished: The function to be called after the upload is successful.
    #  \param on_progress: A function to be called during upload progress. It receives a percentage (0-100).
    #  \param on_error: A function to be called if the upload fails.
    def uploadToolPath(self, print_job: CloudPrintJobResponse, mesh: bytes,
                       on_finished: Callable[[], Any],
                       on_progress: Callable[[int],
                                             Any], on_error: Callable[[],
                                                                      Any]):
        self._upload = ToolPathUploader(self._manager, print_job, mesh,
                                        on_finished, on_progress, on_error)
        self._upload.start()

    # Requests a cluster to print the given print job.
    #  \param cluster_id: The ID of the cluster.
    #  \param job_id: The ID of the print job.
    #  \param on_finished: The function to be called after the result is parsed.
    def requestPrint(self, cluster_id: str, job_id: str,
                     on_finished: Callable[[CloudPrintResponse], Any]) -> None:
        url = "{}/clusters/{}/print/{}".format(self.CLUSTER_API_ROOT,
                                               cluster_id, job_id)
        reply = self._manager.post(self._createEmptyRequest(url), b"")
        self._addCallback(reply, on_finished, CloudPrintResponse)

    ##  Send a print job action to the cluster for the given print job.
    #  \param cluster_id: The ID of the cluster.
    #  \param cluster_job_id: The ID of the print job within the cluster.
    #  \param action: The name of the action to execute.
    def doPrintJobAction(self,
                         cluster_id: str,
                         cluster_job_id: str,
                         action: str,
                         data: Optional[Dict[str, Any]] = None) -> None:
        body = b""
        if data:
            try:
                body = json.dumps({"data": data}).encode()
            except JSONDecodeError as err:
                Logger.log("w", "Could not encode body: %s", err)
                return
        url = "{}/clusters/{}/print_jobs/{}/action/{}".format(
            self.CLUSTER_API_ROOT, cluster_id, cluster_job_id, action)
        self._manager.post(self._createEmptyRequest(url), body)

    ##  We override _createEmptyRequest in order to add the user credentials.
    #   \param url: The URL to request
    #   \param content_type: The type of the body contents.
    def _createEmptyRequest(self,
                            path: str,
                            content_type: Optional[str] = "application/json"
                            ) -> QNetworkRequest:
        request = QNetworkRequest(QUrl(path))
        if content_type:
            request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
        access_token = self._account.accessToken
        if access_token:
            request.setRawHeader(b"Authorization",
                                 "Bearer {}".format(access_token).encode())
        return request

    ## Parses the given JSON network reply into a status code and a dictionary, handling unexpected errors as well.
    #  \param reply: The reply from the server.
    #  \return A tuple with a status code and a dictionary.
    @staticmethod
    def _parseReply(reply: QNetworkReply) -> Tuple[int, Dict[str, Any]]:
        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        try:
            response = bytes(reply.readAll()).decode()
            return status_code, json.loads(response)
        except (UnicodeDecodeError, JSONDecodeError, ValueError) as err:
            error = CloudError(code=type(err).__name__,
                               title=str(err),
                               http_code=str(status_code),
                               id=str(time()),
                               http_status="500")
            Logger.logException("e",
                                "Could not parse the stardust response: %s",
                                error.toDict())
            return status_code, {"errors": [error.toDict()]}

    ## Parses the given models and calls the correct callback depending on the result.
    #  \param response: The response from the server, after being converted to a dict.
    #  \param on_finished: The callback in case the response is successful.
    #  \param model_class: The type of the model to convert the response to. It may either be a single record or a list.
    def _parseModels(self, response: Dict[str, Any],
                     on_finished: Union[Callable[[CloudApiClientModel], Any],
                                        Callable[[List[CloudApiClientModel]],
                                                 Any]],
                     model_class: Type[CloudApiClientModel]) -> None:
        if "data" in response:
            data = response["data"]
            if isinstance(data, list):
                results = [model_class(**c)
                           for c in data]  # type: List[CloudApiClientModel]
                on_finished_list = cast(
                    Callable[[List[CloudApiClientModel]], Any], on_finished)
                on_finished_list(results)
            else:
                result = model_class(**data)  # type: CloudApiClientModel
                on_finished_item = cast(Callable[[CloudApiClientModel], Any],
                                        on_finished)
                on_finished_item(result)
        elif "errors" in response:
            self._on_error(
                [CloudError(**error) for error in response["errors"]])
        else:
            Logger.log("e",
                       "Cannot find data or errors in the cloud response: %s",
                       response)

    ## Creates a callback function so that it includes the parsing of the response into the correct model.
    #  The callback is added to the 'finished' signal of the reply.
    #  \param reply: The reply that should be listened to.
    #  \param on_finished: The callback in case the response is successful. Depending on the endpoint it will be either
    #       a list or a single item.
    #  \param model: The type of the model to convert the response to.
    def _addCallback(
        self,
        reply: QNetworkReply,
        on_finished: Union[Callable[[CloudApiClientModel], Any],
                           Callable[[List[CloudApiClientModel]], Any]],
        model: Type[CloudApiClientModel],
    ) -> None:
        def parse() -> None:
            # Don't try to parse the reply if we didn't get one
            if reply.attribute(
                    QNetworkRequest.HttpStatusCodeAttribute) is None:
                return
            status_code, response = self._parseReply(reply)
            self._anti_gc_callbacks.remove(parse)
            self._parseModels(response, on_finished, model)
            return

        self._anti_gc_callbacks.append(parse)
        reply.finished.connect(parse)
예제 #29
0
파일: daemon.py 프로젝트: hydrargyrum/eye
class Ycm(QObject, CategoryMixin):
    """YCMD instance control"""

    YCMD_CMD = ['ycmd']
    """Base ycmd command.

	Useful if ycmd is not in `PATH` or set permanent arguments
	"""

    IDLE_SUICIDE = 120
    """Maximum time after which ycmd should quit if it has received no requests.

	A periodic ping is sent by `Ycm` objects.
	"""

    CHECK_REPLY_SIGNATURE = True
    TIMEOUT = 10

    def __init__(self, **kwargs):
        super(Ycm, self).__init__(**kwargs)

        self.addr = None
        """Address of the ycmd server."""

        self.port = 0
        """TCP port of the ycmd server."""

        self._ready = False
        self.secret = ''
        self.config = {}

        self.proc = QProcess()
        self.proc.started.connect(self.procStarted)
        self.proc.errorOccurred.connect(self.procError)
        self.proc.finished.connect(self.procFinished)

        self.pingTimer = QTimer(self)
        self.pingTimer.timeout.connect(self.ping)

        self.network = QNetworkAccessManager()

        qApp().aboutToQuit.connect(self.stop)

        self.addCategory('ycm_control')

    def makeConfig(self):
        self.secret = generate_key()
        self.config['hmac_secret'] = b64encode(self.secret).decode('ascii')

        fd, path = tempfile.mkstemp()
        with open(path, 'w') as fd:
            fd.write(json.dumps(self.config))
            fd.flush()
        return path

    def checkReply(self, reply):
        """Check the ycmd reply is a success.

		Checks the `reply` has a HTTP 200 status code and the signature is valid.
		In case of error, raises a :any:`ServerError`.

		:type reply: QNetworkReply
		"""
        reply.content = bytes(reply.readAll())

        if reply.error():
            raise ServerError(reply.error() + 1000, reply.errorString(),
                              reply.content)
        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        if status_code != 200:
            data = reply.content.decode('utf-8')
            try:
                data = json.loads(data)
            except (ValueError, JSONDecodeError):
                LOGGER.info('ycmd replied non-json body: %r', data)

            raise ServerError(status_code, data)

        if not self.CHECK_REPLY_SIGNATURE:
            return
        actual = b64decode(bytes(reply.rawHeader(HMAC_HEADER)))
        expected = self._hmacDigest(reply.content)

        if not hmac.compare_digest(expected, actual):
            raise RuntimeError('Server signature did not match')

    def _jsonReply(self, reply):
        body = reply.content.decode('utf-8')
        return json.loads(body)

    def _hmacDigest(self, msg):
        return hmac.new(self.secret, msg, hashlib.sha256).digest()

    def _sign(self, verb, path, body=b''):
        digests = [self._hmacDigest(part) for part in [verb, path, body]]
        return self._hmacDigest(b''.join(digests))

    def _doGet(self, path):
        url = urlunsplit(('http', self.addr, path, '', ''))
        sig = self._sign(b'GET', path.encode('utf-8'), b'')
        headers = {HMAC_HEADER: b64encode(sig)}

        request = QNetworkRequest(QUrl(url))
        for hname in headers:
            request.setRawHeader(hname, headers[hname])

        LOGGER_REQUESTS.debug('GET %r', url)
        reply = self.network.get(request)
        return reply

    def _doPost(self, path, **params):
        url = urlunsplit(('http', self.addr, path, '', ''))
        body = json.dumps(params).encode('utf-8')
        sig = self._sign(b'POST', path.encode('utf-8'), body)
        headers = {
            HMAC_HEADER: b64encode(sig),
            b'Content-Type': b'application/json'
        }

        request = QNetworkRequest(QUrl(url))
        for hname in headers:
            request.setRawHeader(hname, headers[hname])
        LOGGER_REQUESTS.debug('POST %r with data %r', url, body)
        reply = self.network.post(request, body)
        return reply

    def ping(self):
        def handleReply():
            self.checkReply(reply)
            if not self._ready:
                self._ready = True
                self.pingTimer.start(60000)
                self.ready.emit()

        reply = self._doGet('/healthy')
        reply.finished.connect(handleReply)
        reply.finished.connect(reply.deleteLater)

    def start(self):
        if not self.port:
            self.port = generate_port()
        self.addr = 'localhost:%s' % self.port
        path = self.makeConfig()

        _, outlogpath = tempfile.mkstemp(prefix='eye-ycm', suffix='.out.log')
        _, errlogpath = tempfile.mkstemp(prefix='eye-ycm', suffix='.err.log')
        LOGGER.info('ycmd will log to %r and %r', outlogpath, errlogpath)

        cmd = (self.YCMD_CMD + [
            '--idle_suicide_seconds',
            str(self.IDLE_SUICIDE),
            '--port',
            str(self.port),
            '--options_file',
            path,
            '--stdout',
            outlogpath,
            '--stderr',
            errlogpath,
        ])

        LOGGER.debug('will run %r', cmd)

        self.proc.start(cmd[0], cmd[1:])

        self._ready = False

    @Slot()
    def stop(self, wait=0.2):
        if self.proc.state() == QProcess.NotRunning:
            return
        self.proc.terminate()
        if self.proc.state() == QProcess.NotRunning:
            return
        time.sleep(wait)
        self.proc.kill()

    def isRunning(self):
        return self.proc.state() == QProcess.Running

    def connectTo(self, addr):
        self.addr = addr
        self._ready = False
        self.pingTimer.start(1000)

    @Slot()
    def procStarted(self):
        LOGGER.debug('daemon has started')
        self.pingTimer.start(1000)

    @Slot(int, QProcess.ExitStatus)
    def procFinished(self, code, status):
        LOGGER.info('daemon has exited with status %r and code %r', status,
                    code)
        self.pingTimer.stop()
        self._ready = False

    @Slot(QProcess.ProcessError)
    def procError(self, error):
        LOGGER.warning('daemon failed to start (%r): %s', error,
                       self.errorString())

    ready = Signal()

    def _commonPostDict(self, filepath, filetype, contents, line=1, column=1):
        d = {
            'filepath': filepath,
            'filetype': filetype,
            'file_data': {
                filepath: {
                    'filetypes': [filetype],
                    'contents': contents
                }
            },
            'line_num': line,
            'column_num': column,
        }
        return d

    def _postSimpleRequest(self, urlpath, filepath, filetype, contents,
                           **kwargs):
        d = self._commonPostDict(filepath, filetype, contents)
        d.update(**kwargs)

        return self._doPost(urlpath, **d)

    def acceptExtraConf(self, filepath, filetype, contents):
        reply = self._postSimpleRequest('/load_extra_conf_file', filepath,
                                        filetype, contents)
        reply.finished.connect(reply.deleteLater)

    def rejectExtraConf(self, filepath, filetype, contents):
        reply = self._postSimpleRequest('/ignore_extra_conf_file',
                                        filepath,
                                        filetype,
                                        contents,
                                        _ignore_body=True)
        reply.finished.connect(reply.deleteLater)

    def sendParse(self, filepath, filetype, contents, retry_extra=True):
        d = {'event_name': 'FileReadyToParse'}
        reply = self._postSimpleRequest('/event_notification', filepath,
                                        filetype, contents, **d)

        def handleReply():
            try:
                self.checkReply(reply)
            except ServerError as exc:
                excdata = exc.args[1]
                if (isinstance(excdata, dict) and 'exception' in excdata
                        and excdata['exception']['TYPE'] == 'UnknownExtraConf'
                        and retry_extra):
                    confpath = excdata['exception']['extra_conf_file']
                    LOGGER.info(
                        'ycmd encountered %r and wonders if it should be loaded',
                        confpath)

                    accepted = sendIntent(None,
                                          'queryExtraConf',
                                          conf=confpath)
                    if accepted:
                        LOGGER.info('extra conf %r will be loaded', confpath)
                        self.acceptExtraConf(confpath, filetype, contents)
                    else:
                        LOGGER.info('extra conf %r will be rejected', confpath)
                        self.rejectExtraConf(confpath, filetype, contents)

                    return self.sendParse(filepath,
                                          filetype,
                                          contents,
                                          retry_extra=False)
                raise

        reply.finished.connect(handleReply)
        reply.finished.connect(reply.deleteLater)

    def querySubcommandsList(self, filepath, filetype, contents, line, col):
        return self._postSimpleRequest('/defined_subcommands', filepath,
                                       filetype, contents)

    def querySubcommand(self, filepath, filetype, contents, line, col, *args):
        d = {
            'command_arguments': list(args),
            'line_num': line,
            'column_num': col,
        }
        return self._postSimpleRequest('/run_completer_command', filepath,
                                       filetype, contents, **d)

    def queryCompletions(self, filepath, filetype, contents, line, col):
        d = {
            'line_num': line,
            'column_num': col,
        }
        return self._postSimpleRequest('/completions', filepath, filetype,
                                       contents, **d)

    if 0:

        def queryDiagnostic(self, filepath, filetype, contents, line, col):
            return self._postSimpleRequest('/detailed_diagnostic', filepath,
                                           filetype, contents)

        def queryDebug(self, filepath, filetype, contents, line, col):
            return self._postSimpleRequest('/debug_info', filepath, filetype,
                                           contents)
class OctoPrintOutputDevice(PrinterOutputDevice):
    def __init__(self, key, address, properties):
        super().__init__(key)
        self._address = address
        self._key = key
        self._properties = properties  # Properties dict as provided by zero conf

        self._gcode = None

        ##  Todo: Hardcoded value now; we should probably read this from the machine definition and octoprint.
        self._num_extruders = 1

        self._hotend_temperatures = [0] * self._num_extruders
        self._target_hotend_temperatures = [0] * self._num_extruders

        self._api_version = "1"
        self._api_prefix = "/api/"
        self._api_header = "X-Api-Key"
        self._api_key = None

        self.setName(key)
        self.setShortDescription(i18n_catalog.i18nc("@action:button", "Print with OctoPrint"))
        self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print with OctoPrint"))
        self.setIconName("print")

        #   QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
        #   hook itself into the event loop, which results in events never being fired / done.
        self._manager = QNetworkAccessManager()
        self._manager.finished.connect(self._onFinished)

        ##  Hack to ensure that the qt networking stuff isn't garbage collected (unless we want it to)
        self._printer_request = None
        self._printer_reply = None

        self._print_job_request = None
        self._print_job_reply = None

        self._image_request = None
        self._image_reply = None

        self._post_request = None
        self._post_reply = None
        self._post_multi_part = None
        self._post_part = None

        self._job_request = None
        self._job_reply = None

        self._progress_message = None
        self._error_message = None

        self._update_timer = QTimer()
        self._update_timer.setInterval(2000)  # TODO; Add preference for update interval
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)

        self._camera_timer = QTimer()
        self._camera_timer.setInterval(500)  # Todo: Add preference for camera update interval
        self._camera_timer.setSingleShot(False)
        self._camera_timer.timeout.connect(self._update_camera)

        self._camera_image_id = 0

        self._camera_image = QImage()

    def getProperties(self):
        return self._properties

    ##  Get the unique key of this machine
    #   \return key String containing the key of the machine.
    @pyqtSlot(result = str)
    def getKey(self):
        return self._key

    ##  Set the API key of this OctoPrint instance
    def setApiKey(self, api_key):
        self._api_key = api_key

    ##  Name of the printer (as returned from the zeroConf properties)
    @pyqtProperty(str, constant = True)
    def name(self):
        return self._key

    ##  Version (as returned from the zeroConf properties)
    @pyqtProperty(str, constant=True)
    def octoprintVersion(self):
        return self._properties.get(b"version", b"").decode("utf-8")

    ## IPadress of this printer
    @pyqtProperty(str, constant=True)
    def ipAddress(self):
        return self._address

    def _update_camera(self):
        ## Request new image
        url = QUrl("http://" + self._address + ":8080/?action=snapshot")
        self._image_request = QNetworkRequest(url)
        self._image_reply = self._manager.get(self._image_request)

    def _update(self):
        ## Request 'general' printer data
        url = QUrl("http://" + self._address + self._api_prefix + "printer")
        self._printer_request = QNetworkRequest(url)
        self._printer_request.setRawHeader(self._api_header.encode(), self._api_key.encode())
        self._printer_reply = self._manager.get(self._printer_request)

        ## Request print_job data
        url = QUrl("http://" + self._address + self._api_prefix + "job")
        self._job_request = QNetworkRequest(url)
        self._job_request.setRawHeader(self._api_header.encode(), self._api_key.encode())
        self._job_reply = self._manager.get(self._job_request)

    def close(self):
        self.setConnectionState(ConnectionState.closed)
        self._update_timer.stop()
        self._camera_timer.stop()

    def requestWrite(self, node, file_name = None, filter_by_machine = False):
        self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")

        self.startPrint()

    def isConnected(self):
        return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error

    ##  Start requesting data from printer
    def connect(self):
        self.setConnectionState(ConnectionState.connecting)
        self._update()  # Manually trigger the first update, as we don't want to wait a few secs before it starts.
        self._update_camera()
        Logger.log("d", "Connection with printer %s with ip %s started", self._key, self._address)
        self._update_timer.start()
        self._camera_timer.start()

    newImage = pyqtSignal()

    @pyqtProperty(QUrl, notify = newImage)
    def cameraImage(self):
        self._camera_image_id += 1
        temp = "image://camera/" + str(self._camera_image_id)
        return QUrl(temp, QUrl.TolerantMode)

    def getCameraImage(self):
        return self._camera_image

    def _setJobState(self, job_state):
        url = QUrl("http://" + self._address + self._api_prefix + "job")
        self._job_request = QNetworkRequest(url)
        self._job_request.setRawHeader(self._api_header.encode(), self._api_key.encode())
        self._job_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
        if job_state == "abort":
            command = "cancel"
        elif job_state == "print":
            if self.jobState == "paused":
                command = "pause"
            else:
                command = "start"
        elif job_state == "pause":
            command = "pause"
        data = "{\"command\": \"%s\"}" % command
        self._job_reply = self._manager.post(self._job_request, data.encode())
        Logger.log("d", "Sent command to OctoPrint instance: %s", data)

    def startPrint(self):
        if self.jobState != "ready" and self.jobState != "":
            self._error_message = Message(i18n_catalog.i18nc("@info:status", "Printer is printing. Unable to start a new job."))
            self._error_message.show()
            return
        try:
            self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1)
            self._progress_message.show()

            ## Mash the data into single string
            single_string_file_data = ""
            for line in self._gcode:
                single_string_file_data += line

            ##  TODO: Use correct file name (we use placeholder now)
            file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName

            ##  Create multi_part request
            self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)

            ##  Create parts (to be placed inside multipart)
            self._post_part = QHttpPart()
            self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"select\"")
            self._post_part.setBody(b"true")
            self._post_multi_part.append(self._post_part)

            self._post_part = QHttpPart()
            self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"print\"")
            self._post_part.setBody(b"true")
            self._post_multi_part.append(self._post_part)

            self._post_part = QHttpPart()
            self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\"; filename=\"%s\"" % file_name)
            self._post_part.setBody(single_string_file_data.encode())
            self._post_multi_part.append(self._post_part)

            url = QUrl("http://" + self._address + self._api_prefix + "files/local")

            ##  Create the QT request
            self._post_request = QNetworkRequest(url)
            self._post_request.setRawHeader(self._api_header.encode(), self._api_key.encode())

            ##  Post request + data
            self._post_reply = self._manager.post(self._post_request, self._post_multi_part)
            self._post_reply.uploadProgress.connect(self._onUploadProgress)

            self._gcode = None

        except IOError:
            self._progress_message.hide()
            self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to send data to printer. Is another job still active?"))
            self._error_message.show()
        except Exception as e:
            self._progress_message.hide()
            Logger.log("e", "An exception occurred in network connection: %s" % str(e))

    ##  Handler for all requests that have finished.
    def _onFinished(self, reply):
        http_status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        if reply.operation() == QNetworkAccessManager.GetOperation:
            if "printer" in reply.url().toString():  # Status update from /printer.
                if http_status_code == 200:
                    if self._connection_state == ConnectionState.connecting:
                        self.setConnectionState(ConnectionState.connected)
                    json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))

                    # Check for hotend temperatures
                    for index in range(0, self._num_extruders):
                        temperature = json_data["temperature"]["tool%d" % index]["actual"]
                        self._setHotendTemperature(index, temperature)

                    bed_temperature = json_data["temperature"]["bed"]["actual"]
                    self._setBedTemperature(bed_temperature)

                    printer_state = "offline"
                    if json_data["state"]["flags"]["error"]:
                        printer_state = "error"
                    elif json_data["state"]["flags"]["paused"]:
                        printer_state = "paused"
                    elif json_data["state"]["flags"]["printing"]:
                        printer_state = "printing"
                    elif json_data["state"]["flags"]["ready"]:
                        printer_state = "ready"
                    self._updateJobState(printer_state)
                else:
                    pass  # TODO: Handle errors

            elif "job" in reply.url().toString():  # Status update from /job:
                if http_status_code == 200:
                    json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))

                    progress = json_data["progress"]["completion"]
                    if progress:
                        self.setProgress(progress)

                    if json_data["progress"]["printTime"]:
                        self.setTimeElapsed(json_data["progress"]["printTime"])
                        if json_data["progress"]["printTimeLeft"]:
                            self.setTimeTotal(json_data["progress"]["printTime"] + json_data["progress"]["printTimeLeft"])
                        elif json_data["job"]["estimatedPrintTime"]:
                            self.setTimeTotal(json_data["job"]["estimatedPrintTime"])
                        elif progress > 0:
                            self.setTimeTotal(json_data["progress"]["printTime"] / (progress / 100))
                        else:
                            self.setTimeTotal(0)
                    else:
                        self.setTimeElapsed(0)
                        self.setTimeTotal(0)
                    self.setJobName(json_data["job"]["file"]["name"])
                else:
                    pass  # TODO: Handle errors

            elif "snapshot" in reply.url().toString():  # Update from camera:
                if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
                    self._camera_image.loadFromData(reply.readAll())
                    self.newImage.emit()
                else:
                    pass  # TODO: Handle errors

        elif reply.operation() == QNetworkAccessManager.PostOperation:
            if "files" in reply.url().toString():  # Result from /files command:
                if http_status_code == 201:
                    Logger.log("d", "Resource created on OctoPrint instance: %s", reply.header(QNetworkRequest.LocationHeader).toString())
                else:
                    pass  # TODO: Handle errors

                reply.uploadProgress.disconnect(self._onUploadProgress)
                self._progress_message.hide()

            elif "job" in reply.url().toString():  # Result from /job command:
                if http_status_code == 204:
                    Logger.log("d", "Octoprint command accepted")
                else:
                    pass  # TODO: Handle errors

        else:
            Logger.log("d", "OctoPrintOutputDevice got an unhandled operation %s", reply.operation())

    def _onUploadProgress(self, bytes_sent, bytes_total):
        if bytes_total > 0:
            self._progress_message.setProgress(bytes_sent / bytes_total * 100)
        else:
            self._progress_message.setProgress(0)
예제 #31
0
class DiscoverOctoPrintAction(MachineAction):
    def __init__(self, parent: QObject = None) -> None:
        super().__init__("DiscoverOctoPrintAction",
                         catalog.i18nc("@action", "Connect OctoPrint"))

        self._qml_url = os.path.join("qml", "DiscoverOctoPrintAction.qml")

        self._application = CuraApplication.getInstance()
        self._network_plugin = None  # type: Optional[OctoPrintOutputDevicePlugin]

        #   QNetwork manager needs to be created in advance. If we don't it can happen that it doesn't correctly
        #   hook itself into the event loop, which results in events never being fired / done.
        self._network_manager = QNetworkAccessManager()
        self._network_manager.finished.connect(self._onRequestFinished)

        self._settings_reply = None  # type: Optional[QNetworkReply]
        self._settings_reply_timeout = None  # type: Optional[NetworkReplyTimeout]

        self._instance_supports_appkeys = False
        self._appkey_reply = None  # type: Optional[QNetworkReply]
        self._appkey_request = None  # type: Optional[QNetworkRequest]
        self._appkey_instance_id = ""

        self._appkey_poll_timer = QTimer()
        self._appkey_poll_timer.setInterval(500)
        self._appkey_poll_timer.setSingleShot(True)
        self._appkey_poll_timer.timeout.connect(self._pollApiKey)

        # Try to get version information from plugin.json
        plugin_file_path = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), "plugin.json")
        try:
            with open(plugin_file_path) as plugin_file:
                plugin_info = json.load(plugin_file)
                self._plugin_version = plugin_info["version"]
        except:
            # The actual version info is not critical to have so we can continue
            self._plugin_version = "0.0"
            Logger.logException(
                "w", "Could not get version information for the plugin")

        self._user_agent = ("%s/%s %s/%s" %
                            (self._application.getApplicationName(),
                             self._application.getVersion(), "OctoPrintPlugin",
                             self._plugin_version)).encode()

        self._settings_instance = None  # type: Optional[OctoPrintOutputDevice]

        self._instance_responded = False
        self._instance_in_error = False
        self._instance_api_key_accepted = False
        self._instance_supports_sd = False
        self._instance_supports_camera = False
        self._instance_installed_plugins = []  # type: List[str]

        self._power_plugins_manager = PowerPlugins()

        # Load keys cache from preferences
        self._preferences = self._application.getPreferences()
        self._preferences.addPreference("octoprint/keys_cache", "")

        try:
            self._keys_cache = json.loads(
                self._deobfuscateString(
                    self._preferences.getValue("octoprint/keys_cache")))
        except ValueError:
            self._keys_cache = {}  # type: Dict[str, Any]
        if not isinstance(self._keys_cache, dict):
            self._keys_cache = {}  # type: Dict[str, Any]

        self._additional_components = None  # type:Optional[QObject]

        ContainerRegistry.getInstance().containerAdded.connect(
            self._onContainerAdded)
        self._application.engineCreatedSignal.connect(
            self._createAdditionalComponentsView)

    @pyqtProperty(str, constant=True)
    def pluginVersion(self) -> str:
        return self._plugin_version

    @pyqtSlot()
    def startDiscovery(self) -> None:
        if not self._plugin_id:
            return
        if not self._network_plugin:
            self._network_plugin = cast(
                OctoPrintOutputDevicePlugin,
                self._application.getOutputDeviceManager().
                getOutputDevicePlugin(self._plugin_id))
            if not self._network_plugin:
                return
            self._network_plugin.addInstanceSignal.connect(
                self._onInstanceDiscovery)
            self._network_plugin.removeInstanceSignal.connect(
                self._onInstanceDiscovery)
            self._network_plugin.instanceListChanged.connect(
                self._onInstanceDiscovery)
            self.instancesChanged.emit()
        else:
            # Restart bonjour discovery
            self._network_plugin.startDiscovery()

    def _onInstanceDiscovery(self, *args) -> None:
        self.instancesChanged.emit()

    @pyqtSlot(str)
    def removeManualInstance(self, name: str) -> None:
        if not self._network_plugin:
            return

        self._network_plugin.removeManualInstance(name)

    @pyqtSlot(str, str, int, str, bool, str, str)
    def setManualInstance(self,
                          name: str,
                          address: str,
                          port: int,
                          path: str,
                          useHttps: bool,
                          userName: str = "",
                          password: str = "") -> None:
        if not self._network_plugin:
            return

        # This manual printer could replace a current manual printer
        self._network_plugin.removeManualInstance(name)

        self._network_plugin.addManualInstance(name, address, port, path,
                                               useHttps, userName, password)

    def _onContainerAdded(self, container: "ContainerInterface") -> None:
        # Add this action as a supported action to all machine definitions
        if (isinstance(container, DefinitionContainer)
                and container.getMetaDataEntry("type") == "machine"
                and container.getMetaDataEntry("supports_usb_connection")):

            self._application.getMachineActionManager().addSupportedAction(
                container.getId(), self.getKey())

    instancesChanged = pyqtSignal()
    appKeysSupportedChanged = pyqtSignal()
    appKeyReceived = pyqtSignal()
    instanceIdChanged = pyqtSignal()

    @pyqtProperty("QVariantList", notify=instancesChanged)
    def discoveredInstances(self) -> List[Any]:
        if self._network_plugin:
            instances = list(self._network_plugin.getInstances().values())
            instances.sort(key=lambda k: k.name)
            return instances
        else:
            return []

    @pyqtSlot(str)
    def setInstanceId(self, key: str) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if global_container_stack:
            global_container_stack.setMetaDataEntry("octoprint_id", key)

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

        self.instanceIdChanged.emit()

    @pyqtProperty(str, notify=instanceIdChanged)
    def instanceId(self) -> str:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return ""

        return global_container_stack.getMetaDataEntry("octoprint_id", "")

    @pyqtSlot(str)
    def requestApiKey(self, instance_id: str) -> None:
        (instance, base_url, basic_auth_username,
         basic_auth_password) = self._getInstanceInfo(instance_id)
        if not base_url:
            return

        ## Request appkey
        self._appkey_instance_id = instance_id
        self._appkey_request = self._createRequest(
            QUrl(base_url + "plugin/appkeys/request"), basic_auth_username,
            basic_auth_password)
        self._appkey_request.setRawHeader(b"Content-Type", b"application/json")
        data = json.dumps({"app": "Cura"})
        self._appkey_reply = self._network_manager.post(
            self._appkey_request, data.encode())

    @pyqtSlot()
    def cancelApiKeyRequest(self) -> None:
        if self._appkey_reply:
            if self._appkey_reply.isRunning():
                self._appkey_reply.abort()
            self._appkey_reply = None

        self._appkey_request = None  # type: Optional[QNetworkRequest]

        self._appkey_poll_timer.stop()

    def _pollApiKey(self) -> None:
        if not self._appkey_request:
            return
        self._appkey_reply = self._network_manager.get(self._appkey_request)

    @pyqtSlot(str)
    def probeAppKeySupport(self, instance_id: str) -> None:
        (instance, base_url, basic_auth_username,
         basic_auth_password) = self._getInstanceInfo(instance_id)
        if not base_url or not instance:
            return

        instance.getAdditionalData()

        self._instance_supports_appkeys = False
        self.appKeysSupportedChanged.emit()

        appkey_probe_request = self._createRequest(
            QUrl(base_url + "plugin/appkeys/probe"), basic_auth_username,
            basic_auth_password)
        self._appkey_reply = self._network_manager.get(appkey_probe_request)

    @pyqtSlot(str, str)
    def testApiKey(self, instance_id: str, api_key: str) -> None:
        (instance, base_url, basic_auth_username,
         basic_auth_password) = self._getInstanceInfo(instance_id)
        if not base_url:
            return

        self._instance_responded = False
        self._instance_api_key_accepted = False
        self._instance_supports_sd = False
        self._instance_supports_camera = False
        self._instance_installed_plugins = []  # type: List[str]
        self.selectedInstanceSettingsChanged.emit()

        if self._settings_reply:
            if self._settings_reply.isRunning():
                self._settings_reply.abort()
            self._settings_reply = None
        if self._settings_reply_timeout:
            self._settings_reply_timeout = None

        if api_key != "":
            Logger.log(
                "d",
                "Trying to access OctoPrint instance at %s with the provided API key."
                % base_url)

            ## Request 'settings' dump
            settings_request = self._createRequest(
                QUrl(base_url + "api/settings"), basic_auth_username,
                basic_auth_password)
            settings_request.setRawHeader(b"X-Api-Key", api_key.encode())
            self._settings_reply = self._network_manager.get(settings_request)
            self._settings_reply_timeout = NetworkReplyTimeout(
                self._settings_reply, 5000, self._onRequestFailed)

            self._settings_instance = instance

    @pyqtSlot(str)
    def setApiKey(self, api_key: str) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return

        global_container_stack.setMetaDataEntry(
            "octoprint_api_key",
            base64.b64encode(api_key.encode("ascii")).decode("ascii"))

        self._keys_cache[self.instanceId] = api_key
        keys_cache = base64.b64encode(
            json.dumps(self._keys_cache).encode("ascii")).decode("ascii")
        self._preferences.setValue("octoprint/keys_cache", keys_cache)

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

    ##  Get the stored API key of an instance, or the one stored in the machine instance
    #   \return key String containing the key of the machine.
    @pyqtSlot(str, result=str)
    def getApiKey(self, instance_id: str) -> str:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return ""

        if instance_id == self.instanceId:
            api_key = self._deobfuscateString(
                global_container_stack.getMetaDataEntry(
                    "octoprint_api_key", ""))
        else:
            api_key = self._keys_cache.get(instance_id, "")

        return api_key

    selectedInstanceSettingsChanged = pyqtSignal()

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceResponded(self) -> bool:
        return self._instance_responded

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceInError(self) -> bool:
        return self._instance_in_error

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceApiKeyAccepted(self) -> bool:
        return self._instance_api_key_accepted

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceSupportsSd(self) -> bool:
        return self._instance_supports_sd

    @pyqtProperty(bool, notify=selectedInstanceSettingsChanged)
    def instanceSupportsCamera(self) -> bool:
        return self._instance_supports_camera

    @pyqtProperty("QStringList", notify=selectedInstanceSettingsChanged)
    def instanceInstalledPlugins(self) -> List[str]:
        return self._instance_installed_plugins

    @pyqtProperty("QVariantList", notify=selectedInstanceSettingsChanged)
    def instanceAvailablePowerPlugins(self) -> List[Dict[str, str]]:
        available_plugins = self._power_plugins_manager.getAvailablePowerPlugs(
        )
        return [{
            "key": plug_id,
            "text": plug_data["name"]
        } for (plug_id, plug_data) in available_plugins.items()]

    @pyqtProperty(bool, notify=appKeysSupportedChanged)
    def instanceSupportsAppKeys(self) -> bool:
        return self._instance_supports_appkeys

    @pyqtSlot(str, str, str)
    def setContainerMetaDataEntry(self, container_id: str, key: str,
                                  value: str) -> None:
        containers = ContainerRegistry.getInstance().findContainers(
            id=container_id)
        if not containers:
            Logger.log(
                "w",
                "Could not set metadata of container %s because it was not found.",
                container_id)
            return

        containers[0].setMetaDataEntry(key, value)

    @pyqtSlot(bool)
    def applyGcodeFlavorFix(self, apply_fix: bool) -> None:
        global_container_stack = self._application.getGlobalContainerStack()
        if not global_container_stack:
            return

        gcode_flavor = "RepRap (Marlin/Sprinter)" if apply_fix else "UltiGCode"
        if global_container_stack.getProperty("machine_gcode_flavor",
                                              "value") == gcode_flavor:
            # No need to add a definition_changes container if the setting is not going to be changed
            return

        # Make sure there is a definition_changes container to store the machine settings
        definition_changes_container = global_container_stack.definitionChanges
        if definition_changes_container == ContainerRegistry.getInstance(
        ).getEmptyInstanceContainer():
            definition_changes_container = CuraStackBuilder.createDefinitionChangesContainer(
                global_container_stack,
                global_container_stack.getId() + "_settings")

        definition_changes_container.setProperty("machine_gcode_flavor",
                                                 "value", gcode_flavor)

        # Update the has_materials metadata flag after switching gcode flavor
        definition = global_container_stack.getBottom()
        if (not definition or definition.getProperty("machine_gcode_flavor",
                                                     "value") != "UltiGCode"
                or definition.getMetaDataEntry("has_materials", False)):

            # In other words: only continue for the UM2 (extended), but not for the UM2+
            return

        has_materials = global_container_stack.getProperty(
            "machine_gcode_flavor", "value") != "UltiGCode"

        material_container = global_container_stack.material

        if has_materials:
            global_container_stack.setMetaDataEntry("has_materials", True)

            # Set the material container to a sane default
            if material_container == ContainerRegistry.getInstance(
            ).getEmptyInstanceContainer():
                search_criteria = {
                    "type":
                    "material",
                    "definition":
                    "fdmprinter",
                    "id":
                    global_container_stack.getMetaDataEntry(
                        "preferred_material")
                }
                materials = ContainerRegistry.getInstance(
                ).findInstanceContainers(**search_criteria)
                if materials:
                    global_container_stack.material = materials[0]
        else:
            # The metadata entry is stored in an ini, and ini files are parsed as strings only.
            # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
            if "has_materials" in global_container_stack.getMetaData():
                global_container_stack.removeMetaDataEntry("has_materials")

            global_container_stack.material = ContainerRegistry.getInstance(
            ).getEmptyInstanceContainer()

        self._application.globalContainerStackChanged.emit()

    @pyqtSlot(str)
    def openWebPage(self, url: str) -> None:
        QDesktopServices.openUrl(QUrl(url))

    def _createAdditionalComponentsView(self) -> None:
        Logger.log(
            "d",
            "Creating additional ui components for OctoPrint-connected printers."
        )

        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "qml",
                            "OctoPrintComponents.qml")
        self._additional_components = self._application.createQmlComponent(
            path, {"manager": self})
        if not self._additional_components:
            Logger.log(
                "w",
                "Could not create additional components for OctoPrint-connected printers."
            )
            return

        self._application.addAdditionalComponent(
            "monitorButtons",
            self._additional_components.findChild(QObject,
                                                  "openOctoPrintButton"))

    def _onRequestFailed(self, reply: QNetworkReply) -> None:
        if reply.operation() == QNetworkAccessManager.GetOperation:
            if "api/settings" in reply.url().toString(
            ):  # OctoPrint settings dump from /settings:
                Logger.log(
                    "w",
                    "Connection refused or timeout when trying to access OctoPrint at %s"
                    % reply.url().toString())
                self._instance_in_error = True
                self.selectedInstanceSettingsChanged.emit()

    ##  Handler for all requests that have finished.
    def _onRequestFinished(self, reply: QNetworkReply) -> None:

        http_status_code = reply.attribute(
            QNetworkRequest.HttpStatusCodeAttribute)
        if not http_status_code:
            # Received no or empty reply
            self._onRequestFailed(reply)
            return

        if reply.operation() == QNetworkAccessManager.PostOperation:
            if "/plugin/appkeys/request" in reply.url().toString(
            ):  # Initial AppKey request
                if http_status_code == 201 or http_status_code == 202:
                    Logger.log("w", "Start polling for AppKeys decision")
                    if not self._appkey_request:
                        return
                    self._appkey_request.setUrl(
                        reply.header(QNetworkRequest.LocationHeader))
                    self._appkey_request.setRawHeader(b"Content-Type", b"")
                    self._appkey_poll_timer.start()
                elif http_status_code == 404:
                    Logger.log(
                        "w",
                        "This instance of OctoPrint does not support AppKeys")
                    self._appkey_request = None  # type: Optional[QNetworkRequest]
                else:
                    response = bytes(reply.readAll()).decode()
                    Logger.log(
                        "w",
                        "Unknown response when requesting an AppKey: %d. OctoPrint said %s"
                        % (http_status_code, response))
                    self._appkey_request = None  # type: Optional[QNetworkRequest]

        if reply.operation() == QNetworkAccessManager.GetOperation:
            if "/plugin/appkeys/probe" in reply.url().toString(
            ):  # Probe for AppKey support
                if http_status_code == 204:
                    self._instance_supports_appkeys = True
                else:
                    self._instance_supports_appkeys = False
                self.appKeysSupportedChanged.emit()

            if "/plugin/appkeys/request" in reply.url().toString(
            ):  # Periodic AppKey request poll
                if http_status_code == 202:
                    self._appkey_poll_timer.start()
                elif http_status_code == 200:
                    Logger.log("d", "AppKey granted")
                    self._appkey_request = None  # type: Optional[QNetworkRequest]
                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        return

                    api_key = json_data["api_key"]
                    self._keys_cache[self._appkey_instance_id] = api_key

                    global_container_stack = self._application.getGlobalContainerStack(
                    )
                    if global_container_stack:
                        global_container_stack.setMetaDataEntry(
                            "octoprint_api_key",
                            base64.b64encode(
                                api_key.encode("ascii")).decode("ascii"))

                    self.appKeyReceived.emit()
                elif http_status_code == 404:
                    Logger.log("d", "AppKey denied")
                    self._appkey_request = None  # type: Optional[QNetworkRequest]
                else:
                    response = bytes(reply.readAll()).decode()
                    Logger.log(
                        "w",
                        "Unknown response when waiting for an AppKey: %d. OctoPrint said %s"
                        % (http_status_code, response))
                    self._appkey_request = None  # type: Optional[QNetworkRequest]

            if "api/settings" in reply.url().toString(
            ):  # OctoPrint settings dump from /settings:
                self._instance_in_error = False

                if http_status_code == 200:
                    Logger.log("d", "API key accepted by OctoPrint.")
                    self._instance_api_key_accepted = True

                    try:
                        json_data = json.loads(
                            bytes(reply.readAll()).decode("utf-8"))
                    except json.decoder.JSONDecodeError:
                        Logger.log(
                            "w",
                            "Received invalid JSON from octoprint instance.")
                        json_data = {}

                    if "feature" in json_data and "sdSupport" in json_data[
                            "feature"]:
                        self._instance_supports_sd = json_data["feature"][
                            "sdSupport"]

                    if "webcam" in json_data and "streamUrl" in json_data[
                            "webcam"]:
                        stream_url = json_data["webcam"]["streamUrl"]
                        if stream_url:  #not empty string or None
                            self._instance_supports_camera = True

                    if "plugins" in json_data:
                        self._power_plugins_manager.parsePluginData(
                            json_data["plugins"])
                        self._instance_installed_plugins = list(
                            json_data["plugins"].keys())

                    api_key = bytes(reply.request().rawHeader(
                        b"X-Api-Key")).decode("utf-8")
                    self.setApiKey(api_key)  # store api key in key cache
                    if self._settings_instance:
                        self._settings_instance.setApiKey(api_key)
                        self._settings_instance.resetOctoPrintUserName()
                        self._settings_instance.getAdditionalData()
                        self._settings_instance.parseSettingsData(json_data)

                    self._settings_instance = None

                elif http_status_code == 401:
                    Logger.log("d", "Invalid API key for OctoPrint.")
                    self._instance_api_key_accepted = False

                elif http_status_code == 502 or http_status_code == 503:
                    Logger.log("d", "OctoPrint is not running.")
                    self._instance_api_key_accepted = False
                    self._instance_in_error = True

                self._instance_responded = True
                self.selectedInstanceSettingsChanged.emit()

    def _createRequest(self,
                       url: str,
                       basic_auth_username: str = "",
                       basic_auth_password: str = "") -> QNetworkRequest:
        request = QNetworkRequest(url)
        request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
        request.setRawHeader(b"User-Agent", self._user_agent)

        if basic_auth_username and basic_auth_password:
            data = base64.b64encode(
                ("%s:%s" % (basic_auth_username,
                            basic_auth_password)).encode()).decode("utf-8")
            request.setRawHeader(b"Authorization",
                                 ("Basic %s" % data).encode())

        # ignore SSL errors (eg for self-signed certificates)
        ssl_configuration = QSslConfiguration.defaultConfiguration()
        ssl_configuration.setPeerVerifyMode(QSslSocket.VerifyNone)
        request.setSslConfiguration(ssl_configuration)

        return request

    ##  Utility handler to base64-decode a string (eg an obfuscated API key), if it has been encoded before
    def _deobfuscateString(self, source: str) -> str:
        try:
            return base64.b64decode(source.encode("ascii")).decode("ascii")
        except UnicodeDecodeError:
            return source

    def _getInstanceInfo(
        self, instance_id: str
    ) -> Tuple[Optional[OctoPrintOutputDevice], str, str, str]:
        if not self._network_plugin:
            return (None, "", "", "")
        instance = self._network_plugin.getInstanceById(instance_id)
        if not instance:
            return (None, "", "", "")

        return (instance, instance.baseURL, instance.getProperty("userName"),
                instance.getProperty("password"))
예제 #32
0
class E5XmlRpcClient(QObject):
    """
    Class implementing a xmlrpc client for Qt.
    """
    def __init__(self, url, parent=None):
        """
        Constructor
        
        @param url xmlrpc handler URL (string or QUrl)
        @param parent parent object (QObject)
        """
        super(E5XmlRpcClient, self).__init__(parent)
        
        # attributes for the network objects
        self.__networkManager = QNetworkAccessManager(self)
        self.__networkManager.proxyAuthenticationRequired.connect(
            proxyAuthenticationRequired)
        self.__networkManager.finished.connect(self.__replyFinished)
        if SSL_AVAILABLE:
            self.__sslErrorHandler = E5SslErrorHandler(self)
            self.__networkManager.sslErrors.connect(self.__sslErrors)
        
        self.__callmap = {}
        
        self.__request = QNetworkRequest(QUrl(url))
        self.__request.setRawHeader(b"User-Agent", b"E5XmlRpcClient/1.0")
        self.__request.setHeader(QNetworkRequest.ContentTypeHeader, "text/xml")
    
    def setUrl(self, url):
        """
        Public slot to set the xmlrpc handler URL.
        
        @param url xmlrpc handler URL (string or QUrl)
        """
        url = QUrl(url)
        if url.isValid():
            self.__request.setUrl(url)
    
    def call(self, method, args, responseCallback, errorCallback):
        """
        Public method to call the remote server.
        
        @param method name of the remote method to be called (string)
        @param args tuple of method arguments (tuple)
        @param responseCallback method to be called with the returned
            result as a tuple (function)
        @param errorCallback method to be called in case of an error
            with error code and error string (function)
        """
        assert isinstance(args, tuple), \
            "argument must be tuple or Fault instance"
        
        data = xmlrpc.dumps(args, method).encode("utf-8")
        reply = self.__networkManager.post(
            self.__request, QByteArray(data))
        self.__callmap[reply] = (responseCallback, errorCallback)
    
    def abort(self):
        """
        Public method to abort all calls.
        """
        for reply in list(self.__callmap):
            if reply.isRunning():
                reply.abort()
    
    def __sslErrors(self, reply, errors):
        """
        Private slot to handle SSL errors.
        
        @param reply reference to the reply object (QNetworkReply)
        @param errors list of SSL errors (list of QSslError)
        """
        ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0]
        if ignored == E5SslErrorHandler.NotIgnored and reply in self.__callmap:
            self.__callmap[reply][1](xmlrpc.TRANSPORT_ERROR, self.tr(
                "SSL Error"))
    
    def __replyFinished(self, reply):
        """
        Private slot handling the receipt of a reply.
        
        @param reply reference to the finished reply (QNetworkReply)
        """
        if reply not in self.__callmap:
            return
        
        if reply.error() != QNetworkReply.NoError:
            self.__callmap[reply][1](xmlrpc.TRANSPORT_ERROR,
                                     reply.errorString())
        else:
            data = bytes(reply.readAll()).decode("utf-8")
            try:
                data = xmlrpc.loads(data)[0]
                self.__callmap[reply][0](data)
            except xmlrpc.Fault as fault:
                self.__callmap[reply][1](fault.faultCode, fault.faultString)
        
        reply.deleteLater()
        del self.__callmap[reply]
예제 #33
0
class NetWorker(QObject):
    ifcAddrChanged = pyqtSignal(str, name='ifcAddrChanged', arguments=['ip'])
    currentIfcAddrChanged = pyqtSignal()
    wifiStatus = pyqtSignal(str,
                            str,
                            int,
                            int,
                            name='wifiStatus',
                            arguments=['essid', 'freq', 'quality', 'level'])
    wifiStatusChanged = pyqtSignal()

    @pyqtProperty(str, notify=currentIfcAddrChanged)
    def currentIfcAddr(self):
        return self.ifcAddr.toString()

    @pyqtProperty(str, notify=wifiStatusChanged)
    def currentWifiESSID(self):
        return self.essid

    @pyqtProperty(str, notify=wifiStatusChanged)
    def currentWifiFreq(self):
        return self.freq

    @pyqtProperty(int, notify=wifiStatusChanged)
    def currentWifiQuality(self):
        return self.quality

    @pyqtProperty(int, notify=wifiStatusChanged)
    def currentWifiLevel(self):
        return self.level

    def __init__(self, loglevel='WARNING', ifcName='wlan0'):
        QObject.__init__(self)

        self.logger = Logger(name='ratt.networker')
        self.logger.setLogLevelStr(loglevel)
        self.debug = self.logger.isDebug()

        self.mgr = QNetworkAccessManager()
        self.sslConfig = QSslConfiguration()
        self.sslSupported = QSslSocket.supportsSsl()

        self.setAuth()
        self.setSSLCertConfig()

        self.mgr.authenticationRequired.connect(
            self.handleAuthenticationRequired)

        self.ifcName = ifcName
        self.ifcAddr = QHostAddress()

        self.statusTimer = QTimer()
        self.statusTimer.setSingleShot(False)
        self.statusTimer.timeout.connect(self.slotStatusTimer)
        self.statusTimer.start(5000)

        self.essid = ''
        self.freq = ''
        self.quality = 0
        self.level = 0

    def slotStatusTimer(self):
        ip = self.getIfcAddress(ifc=self.ifcName)

        if ip != self.ifcAddr:
            self.ifcAddr = ip
            self.ifcAddrChanged.emit(ip.toString())
            self.currentIfcAddrChanged.emit()

        results = {}
        if self.getWifiStatus(results):
            self.essid = results['essid']
            self.freq = results['freq']
            self.quality = results['quality']
            self.level = results['level']
            self.wifiStatus.emit(self.essid, self.freq, self.quality,
                                 self.level)
            self.wifiStatusChanged.emit()

    # ['wlan0', 'IEEE', '802.11', 'ESSID:"FooBar"', 'Mode:Managed', 'Frequency:2.412',
    # 'GHz', 'Access', 'Point:', '00:11:22:33:44:55', 'Bit', 'Rate=65', 'Mb/s', 'Tx-Power=31',
    # 'dBm', 'Retry', 'short', 'limit:7', 'RTS', 'thr:off', 'Fragment', 'thr:off', 'Power',
    # 'Management:on', 'Link', 'Quality=43/70', 'Signal', 'level=-67', 'dBm', 'Rx', 'invalid',
    # 'nwid:0', 'Rx', 'invalid', 'crypt:0', 'Rx', 'invalid', 'frag:0', 'Tx', 'excessive', 'retries:113',
    # 'Invalid', 'misc:0', 'Missed', 'beacon:0']
    def getWifiStatus(self, res):
        try:
            iw = getoutput('/sbin/iwconfig %s' % self.ifcName)

            fields = iw.split()

            for field in fields:
                if field.find('ESSID') != -1:
                    res['essid'] = field.split('"')[1]
                elif field.find('Frequency') != -1:
                    res['freq'] = field.split(':')[1]
                elif field.find('Quality') != -1:
                    frac = field.split('=')[1]
                    (n, d) = frac.split('/')
                    q = int(n) * 100 / int(d)
                    res['quality'] = q
                elif field.find('level') != -1:
                    res['level'] = int(field.split('=')[1])
        except:
            return False

        return True

    def getIfcAddress(self, ifc):
        myIfc = QNetworkInterface.interfaceFromName(ifc)
        addrList = myIfc.addressEntries()

        for addr in addrList:
            if addr.ip().protocol() == QAbstractSocket.IPv4Protocol:
                return addr.ip()

        return QHostAddress()

    def setAuth(self, user='', password=''):
        self.user = user
        self.password = password

    def setSSLCertConfig(self,
                         enabled=False,
                         caCertFile='',
                         clientCertFile='',
                         clientKeyFile=''):
        self.sslEnabled = enabled

        if self.sslSupported and self.sslEnabled:
            self.caCertFile = caCertFile
            self.clientCertFile = clientCertFile
            self.clientKeyFile = clientKeyFile

            self.configureCerts()

    def get(self, url):
        self.logger.debug('get url=%s' % url.toString())

        request = QNetworkRequest(QUrl(url))
        request.setRawHeader("User-Agent", "RATT")

        if self.sslSupported and self.sslEnabled:
            request.setSslConfiguration(self.sslConfig)

        reply = self.mgr.get(request)

        if self.sslSupported and self.sslEnabled:
            reply.sslErrors.connect(self.handleSSLErrors)

        return reply

    def buildRequest(self, url):
        request = QNetworkRequest(QUrl(url))
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          "application/x-www-form-urlencoded")

        return request

    def buildQuery(self, vars):
        query = str()
        for key in vars:
            value = vars[key]
            sep = '&' if query != '' else ''
            query = query + '%s%s=%s' % (sep, key, value)

        return query

    def post(self, request, query):
        self.logger.debug('post url=%s query=%s' %
                          (request.url().toString(), query))

        if self.sslSupported and self.sslEnabled:
            request.setSslConfiguration(self.sslConfig)

        bytearray = QByteArray()
        bytearray.append(query)

        reply = self.mgr.post(request, bytearray)

        if self.sslSupported and self.sslEnabled:
            self.mgr.sslErrors.connect(self.handleSSLErrors)

        return reply

    def handleSSLErrors(self, reply, errors):
        for err in errors:
            self.logger.error('SSL errors:' + err.errorString())

    def handleAuthenticationRequired(self, reply, authenticator):
        if self.user == '' and self.password == '':
            self.logger.warning(
                'authentication required and no user/password set')

        authenticator.setUser(self.user)
        authenticator.setPassword(self.password)

    def configureCerts(self):
        ## import the private client key
        privateKeyFile = QFile(self.clientKeyFile)
        privateKeyFile.open(QIODevice.ReadOnly)
        privateKey = QSslKey(privateKeyFile, QSsl.Rsa)

        if privateKey.isNull():
            self.logger.warning('SSL private key is null')
        else:
            self.sslConfig.setPrivateKey(privateKey)

        ## import the client certificate
        certFile = QFile(self.clientCertFile)
        certFile.open(QIODevice.ReadOnly)
        cert = QSslCertificate(certFile)
        self.sslConfig.setLocalCertificate(cert)

        ## import the self-signed CA certificate
        caCertFile = QFile(self.caCertFile)
        caCertFile.open(QIODevice.ReadOnly)
        caCert = QSslCertificate(caCertFile)

        ## add self-signed CA cert to the other CA certs
        caCertList = self.sslConfig.caCertificates()
        caCertList.append(caCert)
        self.sslConfig.setCaCertificates(caCertList)
예제 #34
0
class PastebinClient(QObject):

    """A client for http://p.cmpl.cc/ using QNetworkAccessManager.

    Attributes:
        _nam: The QNetworkAccessManager used.

    Class attributes:
        API_URL: The base API URL.

    Signals:
        success: Emitted when the paste succeeded.
                 arg: The URL of the paste, as string.
        error: Emitted when the paste failed.
               arg: The error message, as string.
    """

    API_URL = 'http://paste.the-compiler.org/api/'
    success = pyqtSignal(str)
    error = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._nam = QNetworkAccessManager(self)

    def paste(self, name, title, text, parent=None):
        """Paste the text into a pastebin and return the URL.

        Args:
            name: The username to post as.
            title: The post title.
            text: The text to post.
            parent: The parent paste to reply to.
        """
        data = {
            'text': text,
            'title': title,
            'name': name,
        }
        if parent is not None:
            data['reply'] = parent
        encoded_data = urllib.parse.urlencode(data).encode('utf-8')
        create_url = urllib.parse.urljoin(self.API_URL, 'create')
        request = QNetworkRequest(QUrl(create_url))
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          'application/x-www-form-urlencoded;charset=utf-8')
        reply = self._nam.post(request, encoded_data)
        if reply.isFinished():
            self.on_reply_finished(reply)
        else:
            reply.finished.connect(functools.partial(
                self.on_reply_finished, reply))

    def on_reply_finished(self, reply):
        """Read the data and finish when the reply finished.

        Args:
            reply: The QNetworkReply which finished.
        """
        if reply.error() != QNetworkReply.NoError:
            self.error.emit(reply.errorString())
            return
        try:
            url = bytes(reply.readAll()).decode('utf-8')
        except UnicodeDecodeError:
            self.error.emit("Invalid UTF-8 data received in reply!")
            return
        if url.startswith('http://'):
            self.success.emit(url)
        else:
            self.error.emit("Invalid data received in reply!")
예제 #35
0
class HTTPClient(QObject):
    """An HTTP client based on QNetworkAccessManager.

    Intended for APIs, automatically decodes data.

    Attributes:
        _nam: The QNetworkAccessManager used.
        _timers: A {QNetworkReply: QTimer} dict.

    Signals:
        success: Emitted when the operation succeeded.
                 arg: The received data.
        error: Emitted when the request failed.
               arg: The error message, as string.
    """

    success = pyqtSignal(str)
    error = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._nam = QNetworkAccessManager(self)
        self._timers = {}

    def post(self, url, data=None):
        """Create a new POST request.

        Args:
            url: The URL to post to, as QUrl.
            data: A dict of data to send.
        """
        if data is None:
            data = {}
        encoded_data = urllib.parse.urlencode(data).encode('utf-8')
        request = HTTPRequest(url)
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          'application/x-www-form-urlencoded;charset=utf-8')
        reply = self._nam.post(request, encoded_data)
        self._handle_reply(reply)

    def get(self, url):
        """Create a new GET request.

        Emits success/error when done.

        Args:
            url: The URL to access, as QUrl.
        """
        request = HTTPRequest(url)
        reply = self._nam.get(request)
        self._handle_reply(reply)

    def _handle_reply(self, reply):
        """Handle a new QNetworkReply."""
        if reply.isFinished():
            self.on_reply_finished(reply)
        else:
            timer = QTimer(self)
            timer.setInterval(10000)
            timer.timeout.connect(reply.abort)
            timer.start()
            self._timers[reply] = timer
            reply.finished.connect(
                functools.partial(self.on_reply_finished, reply))

    def on_reply_finished(self, reply):
        """Read the data and finish when the reply finished.

        Args:
            reply: The QNetworkReply which finished.
        """
        timer = self._timers.pop(reply)
        if timer is not None:
            timer.stop()
            timer.deleteLater()
        if reply.error() != QNetworkReply.NoError:
            self.error.emit(reply.errorString())
            return
        try:
            data = bytes(reply.readAll()).decode('utf-8')
        except UnicodeDecodeError:
            self.error.emit("Invalid UTF-8 data received in reply!")
            return
        self.success.emit(data)
예제 #36
0
class ClusterApiClient:

    PRINTER_API_PREFIX = "/api/v1"
    CLUSTER_API_PREFIX = "/cluster-api/v1"

    # In order to avoid garbage collection we keep the callbacks in this list.
    _anti_gc_callbacks = []  # type: List[Callable[[], None]]

    ## Initializes a new cluster API client.
    #  \param address: The network address of the cluster to call.
    #  \param on_error: The callback to be called whenever we receive errors from the server.
    def __init__(self, address: str, on_error: Callable) -> None:
        super().__init__()
        self._manager = QNetworkAccessManager()
        self._address = address
        self._on_error = on_error

    ## Get printer system information.
    #  \param on_finished: The callback in case the response is successful.
    def getSystem(self, on_finished: Callable) -> None:
        url = "{}/system".format(self.PRINTER_API_PREFIX)
        reply = self._manager.get(self._createEmptyRequest(url))
        self._addCallback(reply, on_finished, PrinterSystemStatus)

    ## Get the printers in the cluster.
    #  \param on_finished: The callback in case the response is successful.
    def getPrinters(
            self, on_finished: Callable[[List[ClusterPrinterStatus]],
                                        Any]) -> None:
        url = "{}/printers".format(self.CLUSTER_API_PREFIX)
        reply = self._manager.get(self._createEmptyRequest(url))
        self._addCallback(reply, on_finished, ClusterPrinterStatus)

    ## Get the print jobs in the cluster.
    #  \param on_finished: The callback in case the response is successful.
    def getPrintJobs(
            self, on_finished: Callable[[List[ClusterPrintJobStatus]],
                                        Any]) -> None:
        url = "{}/print_jobs".format(self.CLUSTER_API_PREFIX)
        reply = self._manager.get(self._createEmptyRequest(url))
        self._addCallback(reply, on_finished, ClusterPrintJobStatus)

    ## Move a print job to the top of the queue.
    def movePrintJobToTop(self, print_job_uuid: str) -> None:
        url = "{}/print_jobs/{}/action/move".format(self.CLUSTER_API_PREFIX,
                                                    print_job_uuid)
        self._manager.post(
            self._createEmptyRequest(url),
            json.dumps({
                "to_position": 0,
                "list": "queued"
            }).encode())

    ## Override print job configuration and force it to be printed.
    def forcePrintJob(self, print_job_uuid: str) -> None:
        url = "{}/print_jobs/{}".format(self.CLUSTER_API_PREFIX,
                                        print_job_uuid)
        self._manager.put(self._createEmptyRequest(url),
                          json.dumps({
                              "force": True
                          }).encode())

    ## Delete a print job from the queue.
    def deletePrintJob(self, print_job_uuid: str) -> None:
        url = "{}/print_jobs/{}".format(self.CLUSTER_API_PREFIX,
                                        print_job_uuid)
        self._manager.deleteResource(self._createEmptyRequest(url))

    ## Set the state of a print job.
    def setPrintJobState(self, print_job_uuid: str, state: str) -> None:
        url = "{}/print_jobs/{}/action".format(self.CLUSTER_API_PREFIX,
                                               print_job_uuid)
        # We rewrite 'resume' to 'print' here because we are using the old print job action endpoints.
        action = "print" if state == "resume" else state
        self._manager.put(self._createEmptyRequest(url),
                          json.dumps({
                              "action": action
                          }).encode())

    ## Get the preview image data of a print job.
    def getPrintJobPreviewImage(self, print_job_uuid: str,
                                on_finished: Callable) -> None:
        url = "{}/print_jobs/{}/preview_image".format(self.CLUSTER_API_PREFIX,
                                                      print_job_uuid)
        reply = self._manager.get(self._createEmptyRequest(url))
        self._addCallback(reply, on_finished)

    ## We override _createEmptyRequest in order to add the user credentials.
    #  \param url: The URL to request
    #  \param content_type: The type of the body contents.
    def _createEmptyRequest(self,
                            path: str,
                            content_type: Optional[str] = "application/json"
                            ) -> QNetworkRequest:
        url = QUrl("http://" + self._address + path)
        request = QNetworkRequest(url)
        request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
        if content_type:
            request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
        return request

    ## Parses the given JSON network reply into a status code and a dictionary, handling unexpected errors as well.
    #  \param reply: The reply from the server.
    #  \return A tuple with a status code and a dictionary.
    @staticmethod
    def _parseReply(reply: QNetworkReply) -> Tuple[int, Dict[str, Any]]:
        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        try:
            response = bytes(reply.readAll()).decode()
            return status_code, json.loads(response)
        except (UnicodeDecodeError, JSONDecodeError, ValueError) as err:
            Logger.logException("e",
                                "Could not parse the cluster response: %s",
                                err)
            return status_code, {"errors": [err]}

    ## Parses the given models and calls the correct callback depending on the result.
    #  \param response: The response from the server, after being converted to a dict.
    #  \param on_finished: The callback in case the response is successful.
    #  \param model_class: The type of the model to convert the response to. It may either be a single record or a list.
    def _parseModels(self, response: Dict[str, Any],
                     on_finished: Union[Callable[[ClusterApiClientModel], Any],
                                        Callable[[List[ClusterApiClientModel]],
                                                 Any]],
                     model_class: Type[ClusterApiClientModel]) -> None:
        try:
            if isinstance(response, list):
                results = [model_class(**c) for c in response
                           ]  # type: List[ClusterApiClientModel]
                on_finished_list = cast(
                    Callable[[List[ClusterApiClientModel]], Any], on_finished)
                on_finished_list(results)
            else:
                result = model_class(**response)  # type: ClusterApiClientModel
                on_finished_item = cast(Callable[[ClusterApiClientModel], Any],
                                        on_finished)
                on_finished_item(result)
        except JSONDecodeError:
            Logger.log("e", "Could not parse response from network: %s",
                       str(response))

    ## Creates a callback function so that it includes the parsing of the response into the correct model.
    #  The callback is added to the 'finished' signal of the reply.
    #  \param reply: The reply that should be listened to.
    #  \param on_finished: The callback in case the response is successful.
    def _addCallback(
        self,
        reply: QNetworkReply,
        on_finished: Union[Callable[[ClusterApiClientModel], Any],
                           Callable[[List[ClusterApiClientModel]], Any]],
        model: Type[ClusterApiClientModel] = None,
    ) -> None:
        def parse() -> None:
            self._anti_gc_callbacks.remove(parse)

            # Don't try to parse the reply if we didn't get one
            if reply.attribute(
                    QNetworkRequest.HttpStatusCodeAttribute) is None:
                return

            if reply.error() > 0:
                self._on_error(reply.errorString())
                return

            # If no parse model is given, simply return the raw data in the callback.
            if not model:
                on_finished(reply.readAll())
                return

            # Otherwise parse the result and return the formatted data in the callback.
            status_code, response = self._parseReply(reply)
            self._parseModels(response, on_finished, model)

        self._anti_gc_callbacks.append(parse)
        reply.finished.connect(parse)
예제 #37
0
class TranslatorRequest(QObject):
    """
    Class implementing a synchronous network request handler for translation
    requests.
    """
    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent reference to the parent object (QObject)
        """
        super(TranslatorRequest, self).__init__(parent)
        
        self.__contentTypes = {
            "form": b"application/x-www-form-urlencoded",
            "json": b"application/json",
        }
        
        self.__networkManager = QNetworkAccessManager(self)
        self.__networkManager.proxyAuthenticationRequired.connect(
            proxyAuthenticationRequired)
        
        self.__loop = QEventLoop()
        self.__networkManager.finished.connect(self.__loop.quit)
    
    def get(self, requestUrl, extraHeaders=None):
        """
        Public method to issue a GET request.
        
        @param requestUrl URL of the request (QUrl)
        @keyparam extraHeaders list of tuples of additional headers giving
            header name (string) and header value (string)
        @return server response (QByteArray) or error message (string)
        """
        request = QNetworkRequest(requestUrl)
        request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
        if extraHeaders:
            for name, value in extraHeaders:
                request.setRawHeader(name, value)
        reply = self.__networkManager.get(request)
        if not self.__loop.isRunning():
            self.__loop.exec_()
        if reply.error() != QNetworkReply.NoError:
            return reply.errorString(), False
        else:
            return reply.readAll(), True
    
    def post(self, requestUrl, requestData, dataType="form",
             extraHeaders=None):
        """
        Public method to issue a POST request.
        
        @param requestUrl URL of the request (QUrl)
        @param requestData data of the request (QByteArray)
        @keyparam dataType type of the request data (string)
        @keyparam extraHeaders list of tuples of additional headers giving
            header name (string) and header value (string)
        @return tuple of server response (string) and flag indicating
            success (boolean)
        """
        request = QNetworkRequest(requestUrl)
        request.setRawHeader(b"User-Agent",
                             b"Mozilla/5.0")
        request.setRawHeader(b"Content-Type",
                             self.__contentTypes[dataType])
        request.setRawHeader(b"Content-Length",
                             QByteArray.number(requestData.size()))
        request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
        if extraHeaders:
            for name, value in extraHeaders:
                request.setRawHeader(name, value)
        request.setUrl(requestUrl)
        reply = self.__networkManager.post(request, requestData)
        if not self.__loop.isRunning():
            self.__loop.exec_()
        if reply.error() != QNetworkReply.NoError:
            return reply.errorString(), False
        else:
            return str(reply.readAll(), "utf-8", "replace"), True
예제 #38
0
class CloudApiClient:

    # The cloud URL to use for this remote cluster.
    ROOT_PATH = UltimakerCloudAuthentication.CuraCloudAPIRoot
    CLUSTER_API_ROOT = "{}/connect/v1".format(ROOT_PATH)
    CURA_API_ROOT = "{}/cura/v1".format(ROOT_PATH)

    ## Initializes a new cloud API client.
    #  \param account: The user's account object
    #  \param on_error: The callback to be called whenever we receive errors from the server.
    def __init__(self, account: Account, on_error: Callable[[List[CloudError]], None]) -> None:
        super().__init__()
        self._manager = QNetworkAccessManager()
        self._account = account
        self._on_error = on_error
        self._upload = None  # type: Optional[ToolPathUploader]
        # In order to avoid garbage collection we keep the callbacks in this list.
        self._anti_gc_callbacks = []  # type: List[Callable[[], None]]

    ## Gets the account used for the API.
    @property
    def account(self) -> Account:
        return self._account

    ## Retrieves all the clusters for the user that is currently logged in.
    #  \param on_finished: The function to be called after the result is parsed.
    def getClusters(self, on_finished: Callable[[List[CloudClusterResponse]], Any]) -> None:
        url = "{}/clusters".format(self.CLUSTER_API_ROOT)
        reply = self._manager.get(self._createEmptyRequest(url))
        self._addCallback(reply, on_finished, CloudClusterResponse)

    ## Retrieves the status of the given cluster.
    #  \param cluster_id: The ID of the cluster.
    #  \param on_finished: The function to be called after the result is parsed.
    def getClusterStatus(self, cluster_id: str, on_finished: Callable[[CloudClusterStatus], Any]) -> None:
        url = "{}/clusters/{}/status".format(self.CLUSTER_API_ROOT, cluster_id)
        reply = self._manager.get(self._createEmptyRequest(url))
        self._addCallback(reply, on_finished, CloudClusterStatus)

    ## Requests the cloud to register the upload of a print job mesh.
    #  \param request: The request object.
    #  \param on_finished: The function to be called after the result is parsed.
    def requestUpload(self, request: CloudPrintJobUploadRequest, on_finished: Callable[[CloudPrintJobResponse], Any]
                      ) -> None:
        url = "{}/jobs/upload".format(self.CURA_API_ROOT)
        body = json.dumps({"data": request.toDict()})
        reply = self._manager.put(self._createEmptyRequest(url), body.encode())
        self._addCallback(reply, on_finished, CloudPrintJobResponse)

    ## Uploads a print job tool path to the cloud.
    #  \param print_job: The object received after requesting an upload with `self.requestUpload`.
    #  \param mesh: The tool path data to be uploaded.
    #  \param on_finished: The function to be called after the upload is successful.
    #  \param on_progress: A function to be called during upload progress. It receives a percentage (0-100).
    #  \param on_error: A function to be called if the upload fails.
    def uploadToolPath(self, print_job: CloudPrintJobResponse, mesh: bytes, on_finished: Callable[[], Any],
                       on_progress: Callable[[int], Any], on_error: Callable[[], Any]):
        self._upload = ToolPathUploader(self._manager, print_job, mesh, on_finished, on_progress, on_error)
        self._upload.start()

    # Requests a cluster to print the given print job.
    #  \param cluster_id: The ID of the cluster.
    #  \param job_id: The ID of the print job.
    #  \param on_finished: The function to be called after the result is parsed.
    def requestPrint(self, cluster_id: str, job_id: str, on_finished: Callable[[CloudPrintResponse], Any]) -> None:
        url = "{}/clusters/{}/print/{}".format(self.CLUSTER_API_ROOT, cluster_id, job_id)
        reply = self._manager.post(self._createEmptyRequest(url), b"")
        self._addCallback(reply, on_finished, CloudPrintResponse)

    ##  We override _createEmptyRequest in order to add the user credentials.
    #   \param url: The URL to request
    #   \param content_type: The type of the body contents.
    def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
        request = QNetworkRequest(QUrl(path))
        if content_type:
            request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
        access_token = self._account.accessToken
        if access_token:
            request.setRawHeader(b"Authorization", "Bearer {}".format(access_token).encode())
        return request

    ## Parses the given JSON network reply into a status code and a dictionary, handling unexpected errors as well.
    #  \param reply: The reply from the server.
    #  \return A tuple with a status code and a dictionary.
    @staticmethod
    def _parseReply(reply: QNetworkReply) -> Tuple[int, Dict[str, Any]]:
        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        try:
            response = bytes(reply.readAll()).decode()
            return status_code, json.loads(response)
        except (UnicodeDecodeError, JSONDecodeError, ValueError) as err:
            error = CloudError(code=type(err).__name__, title=str(err), http_code=str(status_code),
                               id=str(time()), http_status="500")
            Logger.logException("e", "Could not parse the stardust response: %s", error.toDict())
            return status_code, {"errors": [error.toDict()]}

    ## Parses the given models and calls the correct callback depending on the result.
    #  \param response: The response from the server, after being converted to a dict.
    #  \param on_finished: The callback in case the response is successful.
    #  \param model_class: The type of the model to convert the response to. It may either be a single record or a list.
    def _parseModels(self, response: Dict[str, Any],
                     on_finished: Union[Callable[[CloudApiClientModel], Any],
                                        Callable[[List[CloudApiClientModel]], Any]],
                     model_class: Type[CloudApiClientModel]) -> None:
        if "data" in response:
            data = response["data"]
            if isinstance(data, list):
                results = [model_class(**c) for c in data]  # type: List[CloudApiClientModel]
                on_finished_list = cast(Callable[[List[CloudApiClientModel]], Any], on_finished)
                on_finished_list(results)
            else:
                result = model_class(**data)  # type: CloudApiClientModel
                on_finished_item = cast(Callable[[CloudApiClientModel], Any], on_finished)
                on_finished_item(result)
        elif "errors" in response:
            self._on_error([CloudError(**error) for error in response["errors"]])
        else:
            Logger.log("e", "Cannot find data or errors in the cloud response: %s", response)

    ## Creates a callback function so that it includes the parsing of the response into the correct model.
    #  The callback is added to the 'finished' signal of the reply.
    #  \param reply: The reply that should be listened to.
    #  \param on_finished: The callback in case the response is successful. Depending on the endpoint it will be either
    #       a list or a single item.
    #  \param model: The type of the model to convert the response to.
    def _addCallback(self,
                     reply: QNetworkReply,
                     on_finished: Union[Callable[[CloudApiClientModel], Any],
                                        Callable[[List[CloudApiClientModel]], Any]],
                     model: Type[CloudApiClientModel],
                     ) -> None:
        def parse() -> None:
            status_code, response = self._parseReply(reply)
            self._anti_gc_callbacks.remove(parse)
            return self._parseModels(response, on_finished, model)

        self._anti_gc_callbacks.append(parse)
        reply.finished.connect(parse)