def _createEmptyRequest(self, target: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
     url = QUrl("http://" + self._address + self._api_prefix + target)
     request = QNetworkRequest(url)
     if content_type is not None:
         request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
     request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
     return request
예제 #2
0
 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
예제 #3
0
    def perform_put(self, endpoint, data, url):
        """
        Perform an HTTP PUT request.

        :param endpoint: the name of the Tribler endpoint.
        :param data: the data/body to send with the request.
        :param url: the url to send the request to.
        """
        request = QNetworkRequest(QUrl(url))
        request.setHeader(QNetworkRequest.ContentTypeHeader, "application/x-www-form-urlencoded")
        reply = self.put(request, data)
        return reply
예제 #4
0
 def _getRequest(self, url):
     request = QNetworkRequest(QUrl(url))
     # key
     request.setRawHeader(b"Key", b"")
     # request.setRawHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
     # request.setRawHeader("Accept-Encoding", "gzip, deflate, sdch")
     # request.setRawHeader("Accept-Language", "zh-CN,zh;q=0.8")
     # header
     request.setHeader(QNetworkRequest.ContentTypeHeader, "application/octet-stream")
     # user agent
     request.setHeader(QNetworkRequest.UserAgentHeader, "Blog/Mzone 1.0")
     return request
예제 #5
0
 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 _setJobState(self, job_state):
        if not self._selected_printer:
            return

        selected_printer_uuid = self._printers_dict[self._selected_printer["unique_name"]]["uuid"]
        if selected_printer_uuid not in self._print_job_by_printer_uuid:
            return

        print_job_uuid = self._print_job_by_printer_uuid[selected_printer_uuid]["uuid"]

        url = QUrl(self._api_base_uri + "print_jobs/" + print_job_uuid + "/action")
        put_request = QNetworkRequest(url)
        put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
        data = '{"action": "' + job_state + '"}'
        self._manager.put(put_request, data.encode())
예제 #7
0
 def getSearchRequestData(cls, term):
     """
     Class method to assemble the search request data structure.
     
     @param term search term (string)
     @return tuple of network request object, operation and parameters
         (QNetworkRequest, QNetworkAccessManager.Operation, QByteArray)
     """
     request = QNetworkRequest(QUrl(cls.SearchUrl))
     request.setHeader(QNetworkRequest.ContentTypeHeader,
                       "application/x-www-form-urlencoded")
     op = QNetworkAccessManager.PostOperation
     params = QByteArray("chain=").append(QUrl.toPercentEncoding(term))
     
     return (request, op, params)
예제 #8
0
 def __getFileScanReportUrl(self, scanId):
     """
     Private method to get the report URL for a file scan.
     
     @param scanId ID of the scan to get the report URL for (string)
     """
     request = QNetworkRequest(QUrl(self.GetUrlReportUrl))
     request.setHeader(QNetworkRequest.ContentTypeHeader,
                       "application/x-www-form-urlencoded")
     params = QByteArray("key={0}&resource={1}".format(
         Preferences.getHelp("VirusTotalServiceKey"), scanId))
     
     import Helpviewer.HelpWindow
     nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager()
     reply = nam.post(request, params)
     reply.finished.connect(self.__getFileScanReportUrlFinished)
     self.__replies.append(reply)
예제 #9
0
    def get(self, url, *, user_agent=None, **kwargs):
        """Start a download with a link URL.

        Args:
            url: The URL to get, as QUrl
            user_agent: The UA to set for the request, or None.
            **kwargs: passed to get_request().

        Return:
            The created DownloadItem.
        """
        if not url.isValid():
            urlutils.invalid_url_error(url, "start download")
            return None
        req = QNetworkRequest(url)
        if user_agent is not None:
            req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)
        return self.get_request(req, **kwargs)
예제 #10
0
 def submitUrl(self, url):
     """
     Public method to submit an URL to be scanned.
     
     @param url url to be scanned (QUrl)
     """
     request = QNetworkRequest(QUrl(self.ScanUrlUrl))
     request.setHeader(QNetworkRequest.ContentTypeHeader,
                       "application/x-www-form-urlencoded")
     params = QByteArray("key={0}&url=".format(
         Preferences.getHelp("VirusTotalServiceKey")))\
         .append(QUrl.toPercentEncoding(url.toString()))
     
     import Helpviewer.HelpWindow
     nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager()
     reply = nam.post(request, params)
     reply.finished.connect(self.__submitUrlFinished)
     self.__replies.append(reply)
예제 #11
0
 def modify_variety_message(self):
     """ 修改品种的交割信息 """
     current_data = self.variety_combobox.currentData()
     print(current_data)
     new_data = {
         "variety": current_data["variety"],
         "variety_en": current_data["variety_en"],
         "last_trade": self.last_trade.toPlainText().strip(),
         "receipt_expire": self.receipt_expire.toPlainText().strip(),
         "delivery_unit": self.delivery_unit.toPlainText().strip(),
         "limit_holding": self.limit_holding.toPlainText().strip(),
     }
     url = SERVER_API + "{}/delivery-message/".format(current_data["variety_en"])
     req = QNetworkRequest(QUrl(url))
     req.setHeader(QNetworkRequest.ContentTypeHeader, "application/json;charset=utf-8")
     req.setRawHeader("Authorization".encode("utf-8"), get_user_token().encode("utf-8"))
     network_manager = getattr(qApp, "_network")
     reply = network_manager.put(req, json.dumps(new_data).encode("utf-8"))
     reply.finished.connect(self.modify_reply)
예제 #12
0
    def get(self, url, **kwargs):
        """Start a download with a link URL.

        Args:
            url: The URL to get, as QUrl
            **kwargs: passed to get_request().

        Return:
            The created DownloadItem.
        """
        if not url.isValid():
            urlutils.invalid_url_error(url, "start download")
            return None

        req = QNetworkRequest(url)
        user_agent = websettings.user_agent(url)
        req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)

        return self.get_request(req, **kwargs)
예제 #13
0
 def get_receipt_source_file(self):
     """ 获取仓单日报数据源文件保存至本地 """
     if self.date is None:
         raise DateValueError("请先使用`set_date`设置`SHFESpider`日期.")
     url = "http://www.dce.com.cn/publicweb/quotesdata/wbillWeeklyQuotes.html"
     form_params = {
         'wbillWeeklyQuotes.variety': 'all',
         'year': str(self.date.year),
         'month': str(self.date.month - 1),
         'day': self.date.strftime("%d"),
     }
     form_data = generate_multipart_data(text_dict=form_params)
     request = QNetworkRequest(QUrl(url))
     request.setHeader(QNetworkRequest.UserAgentHeader,
                       random.choice(USER_AGENTS))
     network_manager = getattr(qApp, "_network")
     reply = network_manager.post(request, form_data)
     reply.finished.connect(self.receipt_source_file_reply)
     form_data.setParent(reply)
예제 #14
0
파일: AgentBrowser.py 프로젝트: daasara/zss
 def onGetData(self, data):
     print("onGetData", data)
     if not data:
         return QMessageBox.critical(self, "错误", "获取信息失败")
     username = self.edit_username.text().strip()
     if not username:
         return QMessageBox.critical(self, "提示", "请输入账号")
     password = self.edit_password.text().strip()
     if not password:
         return QMessageBox.critical(self, "提示", "请输入密码")
     data["username"] = username
     data["password"] = password
     # 开始提交
     manager = self.webPage.networkAccessManager()
     req = QNetworkRequest(self.Url_PCI)
     req.setHeader(QNetworkRequest.ContentTypeHeader,
                   "application/x-www-form-urlencoded")
     reply = manager.post(req, urlencode(data).encode())
     reply.finished.connect(lambda: self.replyFinished(reply))
    def _setJobState(self, job_state):
        if not self._selected_printer:
            return

        selected_printer_uuid = self._printers_dict[
            self._selected_printer["unique_name"]]["uuid"]
        if selected_printer_uuid not in self._print_job_by_printer_uuid:
            return

        print_job_uuid = self._print_job_by_printer_uuid[
            selected_printer_uuid]["uuid"]

        url = QUrl(self._api_base_uri + "print_jobs/" + print_job_uuid +
                   "/action")
        put_request = QNetworkRequest(url)
        put_request.setHeader(QNetworkRequest.ContentTypeHeader,
                              "application/json")
        data = '{"action": "' + job_state + '"}'
        self._manager.put(put_request, data.encode())
예제 #16
0
    def confirm_current_folder(self):
        """ 确定当前配置 """
        # 发起配置的请求
        folder_path = self.folder_edit.text()
        if not folder_path:
            return
        body_data = {
            "client": get_client_uuid(),
            "folder_path": folder_path,
            "variety_en": self.variety_en,
            "group_id": self.group_id
        }
        network_manager = getattr(qApp, "_network")
        url = SERVER_API + "industry/user-folder/"
        request = QNetworkRequest(QUrl(url))
        request.setHeader(QNetworkRequest.ContentTypeHeader, "application/x-www-form-urlencoded")
        request.setRawHeader("Authorization".encode("utf-8"), get_user_token().encode("utf-8"))
        reply = network_manager.post(request, json.dumps(body_data).encode("utf-8"))
        reply.finished.connect(self.create_update_folder_reply)

        """ 本地保存(为了更新好处理和用户重新安装程序也存在这个配置,2020-09-29采用线上服务器保存)
예제 #17
0
def SearchPTH(queryString, language):
    request = QNetworkRequest()
    request.setUrl(QUrl("https://paikkatietojentuottajat-koekaytto.maanmittauslaitos.fi/api/public/v1/search?X-CLIENT-LANG=FI"))
    request.setHeader(request.ContentTypeHeader, "application/json")
    jsonByteArray = QJsonDocument(createJson(queryString, language)).toJson()

    blockingNetworkRequest = QgsBlockingNetworkRequest()
    err = blockingNetworkRequest.post(request, jsonByteArray, True)

    if not err:
        response = blockingNetworkRequest.reply().content()
        dict_str = response.data().decode("utf-8")
        if dict_str:
            resp_json = json.loads(dict_str)
            return resp_json.get("hits")
        else:
            #No result returned
            LOG("Nothing returned")
            pass
    else:
        LOG(blockingNetworkRequest.errorMessage())
예제 #18
0
    def modify_row_data(self):
        """ 修改数据表的单元格点击 """
        btn = self.sender()
        row = getattr(btn, 'row_index')
        # 获取组织数据
        item = {
            "id": int(self.modify_table.item(row, 0).text()),
            "date": self.modify_table.item(row, 1).text(),
            "variety_en": self.modify_table.item(row, 2).text(),
            "spot_price": float(self.modify_table.item(row, 3).text()),
            "price_increase": float(self.modify_table.item(row, 4).text())
        }
        network_manager = getattr(qApp, "_network")
        url = SERVER_API + "spot-price/{}/".format(item["id"])

        request = QNetworkRequest(QUrl(url))
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          "application/json;charset=utf-8")

        reply = network_manager.put(request, json.dumps(item).encode("utf-8"))
        reply.finished.connect(self.modify_spot_price_reply)
예제 #19
0
 def checkServiceKeyValidity(self, key, protocol=""):
     """
     Public method to check the validity of the given service key.
     
     @param key service key (string)
     @param protocol protocol used to access VirusTotal (string)
     """
     if protocol == "":
         urlStr = self.GetFileReportUrl
     else:
         urlStr = self.GetFileReportPattern.format(protocol)
     request = QNetworkRequest(QUrl(urlStr))
     request.setHeader(QNetworkRequest.ContentTypeHeader,
                       "application/x-www-form-urlencoded")
     params = QByteArray("key={0}&resource={1}".format(
         key, self.TestServiceKeyScanID))
     
     import Helpviewer.HelpWindow
     nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager()
     reply = nam.post(request, params)
     reply.finished.connect(self.__checkServiceKeyValidityFinished)
     self.__replies.append(reply)
def _post_data(url: str,
               data_: typing.Dict,
               network_manager: QgsNetworkAccessManager,
               auth_config: str,
               feedback: typing.Optional[QgsFeedback] = None):
    request = QNetworkRequest(QUrl(url))
    request.setHeader(QNetworkRequest.ContentTypeHeader, 'application/json')
    reply = network_manager.blockingPost(request,
                                         json.dumps(data_).encode('utf-8'),
                                         auth_config,
                                         True,
                                         feedback=feedback)
    status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
    raw_string_contents = bytes(reply.content()).decode('utf-8')
    if status_code == 201:
        result = json.loads(raw_string_contents)
    else:
        raise QgsProcessingException(f'POST request failed. '
                                     f'status_code: {status_code} - '
                                     f'error_string: {reply.errorString()} - '
                                     f'reply_contents: {raw_string_contents}')
    return result
예제 #21
0
    def checkServiceKeyValidity(self, key, protocol=""):
        """
        Public method to check the validity of the given service key.
        
        @param key service key (string)
        @param protocol protocol used to access VirusTotal (string)
        """
        if protocol == "":
            urlStr = self.GetFileReportUrl
        else:
            urlStr = self.GetFileReportPattern.format(protocol)
        request = QNetworkRequest(QUrl(urlStr))
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          "application/x-www-form-urlencoded")
        params = QByteArray("key={0}&resource={1}".format(
            key, self.TestServiceKeyScanID))

        import Helpviewer.HelpWindow
        nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager()
        reply = nam.post(request, params)
        reply.finished.connect(self.__checkServiceKeyValidityFinished)
        self.__replies.append(reply)
def get_resource(
    name: str,
    dominode_base_url: str,
    network_manager: QgsNetworkAccessManager,
    feedback: typing.Optional[QgsFeedback] = None,
) -> typing.Optional[typing.Dict]:
    url_query = QUrlQuery()
    url_query.addQueryItem('name', name)
    url = QUrl(
        f'{dominode_base_url}/dominode-validation/api/dominode-resources/')
    url.setQuery(url_query)
    request = QNetworkRequest(url)
    request.setHeader(QNetworkRequest.ContentTypeHeader, 'application/json')
    reply = network_manager.blockingGet(request, '', True, feedback=feedback)
    status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
    result = None
    if status_code == 200:
        raw_string_contents = bytes(reply.content()).decode('utf-8')
        contents = json.loads(raw_string_contents)
        exists = contents.get('count', 0) > 0
        if exists:
            result = contents['results'][0]
    return result
예제 #23
0
    def get(self, url, cache=True, **kwargs):
        """Start a download with a link URL.

        Args:
            url: The URL to get, as QUrl
            cache: If set to False, don't cache the response.
            **kwargs: passed to get_request().

        Return:
            The created DownloadItem.
        """
        if not url.isValid():
            urlutils.invalid_url_error(url, "start download")
            return None

        req = QNetworkRequest(url)
        user_agent = websettings.user_agent(url)
        req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)

        if not cache:
            req.setAttribute(QNetworkRequest.CacheSaveControlAttribute, False)

        return self.get_request(req, **kwargs)
예제 #24
0
    def get_daily_source_file(self):
        """ 获取日交易源数据xls文件 """
        if self.date is None:
            raise DateValueError("请先使用`set_date`设置`DCESpider`日期.")
        url = "http://www.dce.com.cn/publicweb/quotesdata/exportDayQuotesChData.html"
        form_params = {
            "dayQuotes.variety": "all",
            "dayQuotes.trade_type": "0",
            "year": str(self.date.year),
            "month": str(self.date.month - 1),
            "day": self.date.strftime("%d"),
            "exportFlag": "excel"
        }
        form_data = generate_multipart_data(text_dict=form_params)

        network_manager = getattr(qApp, "_network")

        request = QNetworkRequest(QUrl(url))
        request.setHeader(QNetworkRequest.UserAgentHeader,
                          random.choice(USER_AGENTS))
        reply = network_manager.post(request, form_data)
        reply.finished.connect(self.daily_source_file_reply)
        form_data.setParent(reply)
예제 #25
0
 def _get(self, url):
     """设置图片或者请求网络图片
     :param url:
     """
     if not url:
         self.onError('')
         return
     if url.startswith('http') and not self.loadingTimer.isActive():
         url = QUrl(url)
         request = QNetworkRequest(url)
         request.setHeader(QNetworkRequest.UserAgentHeader, b'CAvatar')
         request.setRawHeader(b'Author', b'Irony')
         request.setAttribute(QNetworkRequest.FollowRedirectsAttribute,
                              True)
         if qApp._network.cache():
             request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
                                  QNetworkRequest.PreferNetwork)
             request.setAttribute(QNetworkRequest.CacheSaveControlAttribute,
                                  True)
         reply = qApp._network.get(request)
         self.pradius = 0
         self.loadingTimer.start(50)  # 显示进度动画
         reply.finished.connect(self.onFinished)
         reply.error.connect(self.onError)
         return
     self.pradius = 0
     if os.path.exists(url) and os.path.isfile(url):
         if self.isGif:
             self._movie = QMovie(url, parent=self)
             if self._movie.isValid():
                 self._movie.frameChanged.connect(self._resizeGifPixmap)
                 self._movie.start()
         else:
             self._pixmap = QPixmap(url)
             self._resizePixmap()
     else:
         self.onError('')
예제 #26
0
파일: main.py 프로젝트: iphizic/PyTeleBot
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply, QSslConfiguration, QSsl
from PyQt5.QtCore import QUrl


class ForRec(QNetworkAccessManager):
	def Reqest(self,reply):
		print(reply.readAll())
   

if __name__ == '__main__':

	url = "https://api.telegram.org/bot"

	manager = QNetworkAccessManager()
	request = QNetworkRequest()

	config = QSslConfiguration.defaultConfiguration()
	config.setProtocol(QSsl.SecureProtocols)

	request.setSslConfiguration(config)
	request.setUrl(QUrl(url));
	request.setHeader(QNetworkRequest.ServerHeader, "getUpdates")

	#manager.get(request)




예제 #27
0
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_set = False
        self._num_extruders = 1

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

        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._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._response_timeout_time = 5

    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 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

    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):
        # Check that we aren't in a timeout state
        if self._last_response_time and not self._connection_state_before_timeout:
            if time() - self._last_response_time > 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("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._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):
        Application.getInstance().showPrintMonitor.emit(True)
        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.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 instance %s with ip %s started", self._key, self._address)
        self._update_timer.start()
        self._camera_timer.start()
        self._last_response_time = None
        self.setAcceptsCommands(False)

    ##  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
        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._sendCommand(command)

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

        if self.jobState != "ready" and self.jobState != "":
            self._error_message = Message(i18n_catalog.i18nc("@info:status", "OctoPrint 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 OctoPrint"), 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

            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 global_container_stack.getMetaDataEntry("octoprint_auto_print", True):
                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 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 _sendCommand(self, command):
        url = QUrl("http://" + self._address + self._api_prefix + "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 OctoPrint 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 "printer" in reply.url().toString():  # Status update from /printer.
                if http_status_code == 200:
                    self.setAcceptsCommands(True)

                    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
                        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"]
                        self._setHotendTemperature(index, temperature)

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

                    job_state = "offline"
                    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.setAcceptsCommands(False)
                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(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 "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 global_container_stack and not global_container_stack.getMetaDataEntry("octoprint_auto_print", True):
                    message = Message(catalog.i18nc("@info:status", "Saved to OctoPrint as {1}").format(reply.header(QNetworkRequest.LocationHeader).toString()))
                    message.addAction("open_browser", catalog.i18nc("@action:button", "Open Browser"), "globe", catalog.i18nc("@info:tooltip", "Open browser to OctoPrint."))
                    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", "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:
            progress = bytes_sent / bytes_total * 100
            if progress < 100:
                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("http://" + self._address))
예제 #28
0
 def get_weather(self):
     request = QNetworkRequest(QUrl(f"{self.__baseUrl}/repositories"))
     request.setHeader(QNetworkRequest.ContentTypeHeader,
                       QByteArray(b'application/json'))
     return QgsNetworkAccessManager.instance().blockingGet(request)
예제 #29
0
    def GM_xmlhttpRequest(self, details):
        # we could actually mock a XMLHttpRequest to support progress
        # signals but who really uses them?
        # https://wiki.greasespot.net/GM_xmlhttpRequest
        # qtwebchannel.js calls JSON.stringify in QWebChannel.send() so any
        # method attributes of arguments (eg {'onload':function(...){...;}) are
        # stripped.
        # * handle method, url, headers, data
        # * figure out what headers we need to automatically set (referer, ...)
        # * can we use some qt thing (page.get()?) to do ^
        # * should probably check how cookies are handled
        #   chrome/tampermonkey sends cookies (for the requested domain,
        #   duh) with GM_xhr requests
        # https://openuserjs.org/
        # https://greasyfork.org/en/scripts

        # tampermoney on chrome prompts when a script tries to do a
        # cross-origin request.
        print("==============================================")
        print("GM_xmlhttpRequest")
        print(details)

        if not 'url' in details:
            return

        request_index = details['_qute_gm_request_index']
        if not request_index:
            log.greasemonkey.error(("GM_xmlhttpRequest received request "
                                    "without nonce, skipping."))
            return

        if objreg.get('host-blocker').is_blocked(QUrl(details['url'])):
            return

        # TODO: url might be relative, need to fix on the JS side.
        request = QNetworkRequest(QUrl(details['url']))
        request.setOriginatingObject(self)
        # The C++ docs say the default is to not follow any redirects.
        request.setAttribute(QNetworkRequest.RedirectionTargetAttribute,
                             QNetworkRequest.NoLessSafeRedirectPolicy)
        # TODO: Ensure these headers are encoded to spec if containing eg
        # unicodes
        if 'headers' in details:
            for k, v in details['headers'].items():
                # With this script: https://raw.githubusercontent.com/evazion/translate-pixiv-tags/master/translate-pixiv-tags.user.js
                # One of the headers it 'X-Twitter-Polling': True, which was
                # causing the below to error out because v is a bool. Not sure
                # where that is coming from or what value twitter expects.
                # That script is patching jquery so try with unpatched jquery
                # and see what it does.
                request.setRawHeader(k.encode('ascii'), str(v).encode('ascii'))

        # TODO: Should we allow xhr to set user-agent?
        if not request.header(QNetworkRequest.UserAgentHeader):
            request.setHeader(QNetworkRequest.UserAgentHeader,
                              self.profile.httpUserAgent())

        payload = details.get('data', None)
        if payload:
            # Should check encoding from content-type header?
            payload = payload.encode('utf-8')

        reply = self.nam.sendCustomRequest(
            request,
            details.get('method', 'GET').encode('ascii'), payload)

        if reply.isFinished():
            self.handle_xhr_reply(reply, request_index)
        else:
            reply.finished.connect(
                functools.partial(self.handle_xhr_reply, reply, request_index))
예제 #30
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)
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)
예제 #33
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))
예제 #34
0
    def lookupUrl(self, url, platforms):
        """
        Public method to send an URL to Google for checking.
        
        @param url URL to be checked
        @type QUrl
        @param platforms list of platform types to check against
        @type list of str
        @return tuple containing the list of threat list info objects and
            an error message
        @rtype tuple of (list of ThreatList, str)
        """
        error = ""

        # sanitize the URL by removing user info and query data
        url = url.adjusted(QUrl.RemoveUserInfo | QUrl.RemoveQuery
                           | QUrl.RemoveFragment)
        urlStr = url.toString()

        # check the local cache first
        if urlStr in self.__lookupApiCache:
            if (self.__lookupApiCache[urlStr]["validUntil"] >
                    QDateTime.currentDateTime()):
                # cached entry is still valid
                return self.__lookupApiCache[urlStr]["threatInfo"], error
            else:
                del self.__lookupApiCache[urlStr]

        # no valid entry found, ask the safe browsing server
        requestBody = {
            "client": {
                "clientId": self.ClientId,
                "clientVersion": self.ClientVersion,
            },
            "threatInfo": {
                "threatTypes": SafeBrowsingAPIClient.definedThreatTypes(),
                "platformTypes": platforms,
                "threatEntryTypes":
                SafeBrowsingAPIClient.definedThreatEntryTypes(),
                "threatEntries": [
                    {
                        "url": urlStr
                    },
                ],
            },
        }

        data = QByteArray(json.dumps(requestBody).encode("utf-8"))
        url = QUrl(
            self.GsbUrlTemplate.format("threatMatches:find", self.__apiKey))
        req = QNetworkRequest(url)
        req.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
        reply = WebBrowserWindow.networkManager().post(req, data)

        while reply.isRunning():
            QCoreApplication.processEvents(QEventLoop.AllEvents, 200)
            # max. 200 ms processing

        threats = []
        if reply.error() != QNetworkReply.NoError:
            error = reply.errorString()
            self.networkError.emit(error)
        else:
            res = json.loads(str(reply.readAll(), "utf-8"))
            if res and "matches" in res:
                cacheDuration = 0
                for match in res["matches"]:
                    threatInfo = ThreatList(
                        match["threatType"],
                        match["platformType"],
                        match["threatEntryType"],
                    )
                    threats.append(threatInfo)
                    if "cacheDuration" in match:
                        cacheDurationSec = int(
                            match["cacheDuration"].strip().rstrip("s").split(
                                ".")[0])
                        if cacheDurationSec > cacheDuration:
                            cacheDuration = cacheDurationSec
                if cacheDuration > 0 and bool(threats):
                    validUntil = QDateTime.currentDateTime().addSecs(
                        cacheDuration)
                    self.__lookupApiCache[urlStr] = {
                        "validUntil": validUntil,
                        "threatInfo": threats
                    }

        reply.deleteLater()
        return threats, error
예제 #35
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)
예제 #36
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]
예제 #37
0
class SculptoPrintOutputDevice(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

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

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

        self._base_url = "http://%s:%d/" % (self._address, self._port)
        self._api_url = self._base_url + self._api_prefix

        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 on Sculpto"))
        self.setDescription(
            i18n_catalog.i18nc("@properties:tooltip", "Print on Sculpto"))
        self.setIconName("print")
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connected to Sculpto 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._connection_state_before_timeout = None

        self._is_printing = False
        self.estimated_total = 0
        self.starting_time = 0

        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 SculptoPrint 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 SculptoprintVersion(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(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 SculptoPrint 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 Sculpto was lost. Check your network-connections."
                    ))
                self._connection_message.show()
                self.setConnectionState(ConnectionState.error)

        ## Request 'general' printer data
        url = QUrl(self._base_url + "temperature")
        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._base_url + "progress")
        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.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()

        self._last_response_time = None
        self.setAcceptsCommands(False)
        self.setConnectionText(
            i18n_catalog.i18nc("@info:status",
                               "Connecting to Sculpto 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()

    def _setJobState(self, job_state):
        if job_state == "abort":
            Logger.log("d", "Should abort!")
            return
            #command = "cancel"
        elif job_state == "print":
            if self.jobState == "paused":
                command = "pause"
            else:
                command = "start"
        elif job_state == "pause":
            command = "pause"

        if command:
            self._sendCommand(command)

    def stopPrint(self):
        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=\"cool\"")
        self._post_part.setBody(b"true")
        self._post_multi_part.append(self._post_part)

        url = QUrl(self._base_url + "stop_print")

        ##  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)

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

        self._auto_print = parseBool(
            global_container_stack.getMetaDataEntry("sculptoprint_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",
                    "Sculpto 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 Sculpto"),
                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=\"file\"; filename=\"%s\"" % file_name)
            self._post_part.setBody(single_string_file_data.encode())
            self._post_multi_part.append(self._post_part)

            url = QUrl(self._base_url + "upload_and_print")

            self.setJobName(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 SculptoPrint."))
            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 SculptoPrint 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
        Logger.log("w", "Got response")
        if reply.operation() == QNetworkAccessManager.GetOperation:
            if "temperature" in reply.url().toString(
            ):  # Status update from /temperature.
                if http_status_code == 200:
                    if not self.acceptsCommands:
                        self.setAcceptsCommands(True)
                        self.setConnectionText(
                            i18n_catalog.i18nc(
                                "@info:status",
                                "Connected to SculptoPrint 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"))

                    self._setHotendTemperature(0, json_data['payload'])
                    '''
                    job_state = "offline"
                    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.setAcceptsCommands(False)
                    self.setConnectionText(
                        i18n_catalog.i18nc(
                            "@info:status",
                            "Sculpto on {0} does not allow access to print").
                        format(self._key))
                else:
                    pass  # TODO: Handle errors

            elif "progress" in reply.url().toString(
            ):  # Status update from /progress:
                if http_status_code == 500:
                    self.setTimeElapsed(0)
                    self.setTimeTotal(0)
                    self._updateJobState("ready")
                    self.setProgress(0)
                    self.setJobName("Waiting for Print")
                    self._is_printing = False
                if http_status_code == 200:
                    json_data = json.loads(
                        bytes(reply.readAll()).decode("utf-8"))
                    Logger.log("d",
                               "Progress: {0}".format(json_data["payload"]))
                    self.setProgress(json_data["payload"])
                    self._updateJobState("printing")
                    self._is_printing = True
                    self.setTimeElapsed(time() - self.starting_time)
                    '''
                    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 "upload_and_print" in reply.url().toString(
            ):  # Result from /upload_and_print command:
                if http_status_code == 201:
                    Logger.log("d", "Successfully uploaded and printing!")
                    print_information = Application.getInstance(
                    ).getPrintInformation().currentPrintTime
                    self.estimated_total = int(
                        print_information.getDisplayString(0))
                    self.starting_time = time()
                    self.setTimeTotal(self.estimated_total)
                    self.setTimeElapsed(0)
                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 SculptoPrint as {0}").format(
                                    file_name))
                    else:
                        message = Message(
                            i18n_catalog.i18nc("@info:status",
                                               "Saved to SculptoPrint"))
                    message.addAction(
                        "open_browser",
                        i18n_catalog.i18nc("@action:button",
                                           "Open SculptoPrint..."), "globe",
                        i18n_catalog.i18nc(
                            "@info:tooltip",
                            "Open the SculptoPrint web interface"))
                    message.actionTriggered.connect(
                        self._onMessageActionTriggered)
                    message.show()

            elif "stop_print" in reply.url().toString(
            ):  # Result from /stop_print command:
                if http_status_code == 200:
                    Logger.log("d", "Printing stopped")
                    self._updateJobState("ready")
                    self.setProgress(0)
                    self._is_printing = False
                else:
                    pass  # TODO: Handle errors

        else:
            Logger.log(
                "d", "SculptoPrintOutputDevice 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 gcode on Sculpto and starting print"), 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))
예제 #38
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]
예제 #39
0
    def getFullHashes(self, prefixes, clientState):
        """
        Public method to find full hashes matching hash prefixes.
        
        @param prefixes list of hash prefixes to find
        @type list of str (Python 2) or list of bytes (Python 3)
        @param clientState dictionary of client states with keys like
            (threatType, platformType, threatEntryType)
        @type dict
        @return dictionary containing the list of found hashes and the
            negative cache duration
        @rtype dict
        """
        requestBody = {
            "client": {
                "clientId": self.ClientId,
                "clientVersion": self.ClientVersion,
            },
            "clientStates": [],
            "threatInfo": {
                "threatTypes": [],
                "platformTypes": [],
                "threatEntryTypes": [],
                "threatEntries": [],
            },
        }

        for prefix in prefixes:
            requestBody["threatInfo"]["threatEntries"].append(
                {"hash": base64.b64encode(prefix).decode("ascii")})

        for (threatType, platformType,
             threatEntryType), currentState in (clientState.items()):
            requestBody["clientStates"].append(currentState)
            if threatType not in requestBody["threatInfo"]["threatTypes"]:
                requestBody["threatInfo"]["threatTypes"].append(threatType)
            if (platformType
                    not in requestBody["threatInfo"]["platformTypes"]):
                requestBody["threatInfo"]["platformTypes"].append(platformType)
            if (threatEntryType
                    not in requestBody["threatInfo"]["threatEntryTypes"]):
                requestBody["threatInfo"]["threatEntryTypes"].append(
                    threatEntryType)

        data = QByteArray(json.dumps(requestBody).encode("utf-8"))
        url = QUrl(self.GsbUrlTemplate.format("fullHashes:find",
                                              self.__apiKey))
        req = QNetworkRequest(url)
        req.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
        reply = WebBrowserWindow.networkManager().post(req, data)

        while reply.isRunning():
            QCoreApplication.processEvents(QEventLoop.AllEvents, 200)
            # max. 200 ms processing

        res = []
        if reply.error() != QNetworkReply.NoError:
            self.networkError.emit(reply.errorString())
        else:
            res = self.__extractData(reply)

        reply.deleteLater()
        return res
예제 #40
0
    def buildRequest(self, url):
        request = QNetworkRequest(QUrl(url))
        request.setHeader(QNetworkRequest.ContentTypeHeader,
                          "application/x-www-form-urlencoded")

        return request