def _onUploadSlotCompleted( self, reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if error is not None: Logger.warning(str(error)) self.backup_upload_error_message = self.DEFAULT_UPLOAD_ERROR_MESSAGE self._job_done.set() return if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) >= 300: Logger.warning("Could not request backup upload: %s", HttpRequestManager.readText(reply)) self.backup_upload_error_message = self.DEFAULT_UPLOAD_ERROR_MESSAGE self._job_done.set() return backup_upload_url = HttpRequestManager.readJSON( reply)["data"]["upload_url"] # Upload the backup to storage. HttpRequestManager.getInstance().put( backup_upload_url, data=self._backup_zip, callback=self._uploadFinishedCallback, error_callback=self._uploadFinishedCallback)
def getBackups(self, changed: Callable[[List[Dict[str, Any]]], None]) -> None: def callback( reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if error is not None: Logger.log("w", "Could not get backups: " + str(error)) changed([]) return backup_list_response = HttpRequestManager.readJSON(reply) if "data" not in backup_list_response: Logger.log( "w", "Could not get backups from remote, actual response body was: %s", str(backup_list_response)) changed([]) # empty list of backups return changed(backup_list_response["data"]) HttpRequestManager.getInstance().get(self.BACKUP_URL, callback=callback, error_callback=callback, scope=self._json_cloud_scope)
def abortRequest(self, request_id: str) -> None: """Aborts a single request""" if request_id in self._ongoing_requests and self._ongoing_requests[ request_id]: HttpRequestManager.getInstance().abortRequest( self._ongoing_requests[request_id]) self._ongoing_requests[request_id] = None
def unsunscribeUserFromPackage(self, package_id: str) -> None: """Unsubscribe the user (if logged in) from the package :param package_id: the package identification string """ if self._account.isLoggedIn: HttpRequestManager.getInstance().delete( url=f"{USER_PACKAGES_URL}/{package_id}", scope=self._scope)
def _uploadFinishedCallback(self, reply: QNetworkReply, error: QNetworkReply.NetworkError = None): if not HttpRequestManager.replyIndicatesSuccess(reply, error): Logger.log("w", "Could not upload backup file: %s", HttpRequestManager.readText(reply)) self.backup_upload_error_message = self.DEFAULT_UPLOAD_ERROR_MESSAGE self._job_done.set()
def _subscribe(self, package_id: str) -> None: """You probably don't want to use this directly. All installed packages will be automatically subscribed.""" Logger.debug("Subscribing to {}", package_id) data = "{\"data\": {\"package_id\": \"%s\", \"sdk_version\": \"%s\"}}" % ( package_id, CloudApiModel.sdk_version) HttpRequestManager.getInstance().put( url=CloudApiModel.api_url_user_packages, data=data.encode(), scope=self._scope)
def run(self) -> None: url = self._backup.get("download_url") assert url is not None HttpRequestManager.getInstance().get( url = url, callback = self._onRestoreRequestCompleted, error_callback = self._onRestoreRequestCompleted ) self._job_done.wait() # A job is considered finished when the run function completes
def download(self, package_id: str, url: str, update: bool = False) -> None: """Initiate the download request :param package_id: the package identification string :param url: the URL from which the package needs to be obtained :param update: A flag if this is download request is an update process """ if url == "": url = f"{PACKAGES_URL}/{package_id}/download" def downloadFinished(reply: "QNetworkReply") -> None: self._downloadFinished(package_id, reply, update) def downloadError(reply: "QNetworkReply", error: "QNetworkReply.NetworkError") -> None: self._downloadError(package_id, update, reply, error) self._ongoing_requests[ "download_package"] = HttpRequestManager.getInstance().get( url, scope=self._scope, callback=downloadFinished, error_callback=downloadError)
def test_getBasicAuthSuccess() -> None: time.sleep(0.5) app = QCoreApplication([]) http_request_manager = HttpRequestManager.getInstance() cbo = mock.Mock() def callback(*args, **kwargs): cbo.callback(*args, **kwargs) # quit now so we don't need to wait http_request_manager.callLater(0, app.quit) def error_callback(*args, **kwargs): cbo.callback(*args, **kwargs) # quit now so we don't need to wait http_request_manager.callLater(0, app.quit) request_data = http_request_manager.get( url="http://localhost:8080/auth", headers_dict={"Authorization": "Basic dXNlcjp1c2Vy"}, callback=callback, error_callback=error_callback) # Make sure that if something goes wrong, we quit after 10 seconds http_request_manager.callLater(10.0, app.quit) app.exec() http_request_manager.cleanup() # Remove all unscheduled events cbo.callback.assert_called_once_with(request_data.reply) cbo.error_callback.assert_not_called()
def _parseResponse(self, reply: "QNetworkReply") -> None: """ Parse the response from the package list API request which can update. :param reply: A reply containing information about a number of packages. """ response_data = HttpRequestManager.readJSON(reply) if "data" not in response_data: Logger.error( f"Could not interpret the server's response. Missing 'data' from response data. Keys in response: {response_data.keys()}" ) return if len(response_data["data"]) == 0: return packages = response_data["data"] for package in packages: self._package_manager.addAvailablePackageVersion( package["package_id"], Version(package["package_version"])) package_model = self.getPackageModel(package["package_id"]) if package_model: # Also make sure that the local list knows where to get an update package_model.setDownloadUrl(package["download_url"]) self._ongoing_requests["check_updates"] = None
def test_getFail404() -> None: time.sleep(0.5) app = QCoreApplication([]) http_request_manager = HttpRequestManager.getInstance() cbo = mock.Mock() def callback(*args, **kwargs): cbo.callback(*args, **kwargs) # quit now so we don't need to wait http_request_manager.callLater(0, app.quit) def error_callback(*args, **kwargs): cbo.error_callback(*args, **kwargs) # quit now so we don't need to wait http_request_manager.callLater(0, app.quit) request_data = http_request_manager.get( url="http://localhost:8080/do_not_exist", callback=callback, error_callback=error_callback) # Make sure that if something goes wrong, we quit after 10 seconds http_request_manager.callLater(10.0, app.quit) app.exec() http_request_manager.cleanup() # Remove all unscheduled events cbo.error_callback.assert_called_once_with( request_data.reply, QNetworkReply.ContentNotFoundError) cbo.callback.assert_not_called()
def _parseResponse(self, reply: "QNetworkReply") -> None: """ Parse the response from the package list API request. This converts that response into PackageModels, and triggers the ListModel to update. :param reply: A reply containing information about a number of packages. """ response_data = HttpRequestManager.readJSON(reply) if "data" not in response_data or "links" not in response_data: Logger.error(f"Could not interpret the server's response. Missing 'data' or 'links' from response data. Keys in response: {response_data.keys()}") self.setErrorMessage(catalog.i18nc("@info:error", "Could not interpret the server's response.")) return for package_data in response_data["data"]: try: package = PackageModel(package_data, parent = self) self._connectManageButtonSignals(package) self.appendItem({"package": package}) # Add it to this list model. except RuntimeError: # Setting the ownership of this object to not qml can still result in a RuntimeError. Which can occur when quickly toggling # between de-/constructing RemotePackageLists. This try-except is here to prevent a hard crash when the wrapped C++ object # was deleted when it was still parsing the response continue self._request_url = response_data["links"].get("next", "") # Use empty string to signify that there is no next page. self._ongoing_requests["get_packages"] = None self.setIsLoading(False) self.setHasMore(self._request_url != "")
def __init__(self, parent: Optional["QObject"] = None) -> None: super().__init__(parent) manager = HttpRequestManager.getInstance() self._is_internet_reachable = manager.isInternetReachable # type: bool manager.internetReachableChanged.connect( self._onInternetReachableChanged)
def download(self, model: SubscribedPackagesModel) -> None: if self._started: Logger.error("Download already started. Create a new %s instead", self.__class__.__name__) return manager = HttpRequestManager.getInstance() for item in model.items: package_id = item["package_id"] def finishedCallback(reply: QNetworkReply, pid = package_id) -> None: self._onFinished(pid, reply) def progressCallback(rx: int, rt: int, pid = package_id) -> None: self._onProgress(pid, rx, rt) def errorCallback(reply: QNetworkReply, error: QNetworkReply.NetworkError, pid = package_id) -> None: self._onError(pid) request_data = manager.get( item["download_url"], callback = finishedCallback, download_progress_callback = progressCallback, error_callback = errorCallback, scope = self._scope) self._progress[package_id] = { "received": 0, "total": 1, # make sure this is not considered done yet. Also divByZero-safe "file_written": None, "request_data": request_data, "package_model": item } self._started = True self._progress_message.show()
def test_getTimeout() -> None: time.sleep(0.5) app = QCoreApplication([]) http_request_manager = HttpRequestManager.getInstance() cbo = mock.Mock() def error_callback(*args, **kwargs): cbo.error_callback(*args, **kwargs) # quit now so we don't need to wait http_request_manager.callLater(0, app.quit) request_data = http_request_manager.get( url="http://localhost:8080/timeout", error_callback=error_callback, timeout=4) # Make sure that if something goes wrong, we quit after 10 seconds http_request_manager.callLater(10.0, app.quit) app.exec() http_request_manager.cleanup() # Remove all unscheduled events cbo.error_callback.assert_called_once_with( request_data.reply, QNetworkReply.OperationCanceledError) assert request_data.is_aborted_due_to_timeout
def _statusCallback(self, reply: QNetworkReply, error: QNetworkReply.NetworkError = None): url = reply.request().url().toString() prev_statuses = self._statuses.copy() self._statuses[url] = HttpRequestManager.replyIndicatesSuccess(reply, error) if any(self._statuses.values()) != any(prev_statuses.values()): self.internetReachableChanged.emit()
def _onUploadSlotCompleted(self, reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if HttpRequestManager.safeHttpStatus(reply) >= 300: replyText = HttpRequestManager.readText(reply) Logger.warning("Could not request backup upload: %s", replyText) self.backup_upload_error_message = self.DEFAULT_UPLOAD_ERROR_MESSAGE if HttpRequestManager.safeHttpStatus(reply) == 400: errors = json.loads(replyText)["errors"] if "moreThanMaximum" in [error["code"] for error in errors if error["meta"] and error["meta"]["field_name"] == "backup_size"]: self.backup_upload_error_message = catalog.i18nc("@error:file_size", "The backup exceeds the maximum file size.") self._job_done.set() return if error is not None: Logger.warning("Could not request backup upload: %s", HttpRequestManager.qt_network_error_name(error)) self.backup_upload_error_message = self.DEFAULT_UPLOAD_ERROR_MESSAGE self._job_done.set() return backup_upload_url = HttpRequestManager.readJSON(reply)["data"]["upload_url"] # Upload the backup to storage. HttpRequestManager.getInstance().put( backup_upload_url, data=self._backup_zip, callback=self._uploadFinishedCallback, error_callback=self._uploadFinishedCallback )
def __init__(self, app: CuraApplication, on_error: Callable[[List[CloudError]], None]) -> None: super().__init__() self._app = app self._account = app.getCuraAPI().account self._scope = JsonDecoratorScope(UltimakerCloudScope(app)) self._http = HttpRequestManager.getInstance() self._on_error = on_error self._upload = None # type: Optional[ToolPathUploader]
def deleteBackup(self, backup_id: str, finished_callable: Callable[[bool], None]): def finishedCallback( reply: QNetworkReply, ca: Callable[[bool], None] = finished_callable) -> None: self._onDeleteRequestCompleted(reply, ca) def errorCallback( reply: QNetworkReply, error: QNetworkReply.NetworkError, ca: Callable[[bool], None] = finished_callable) -> None: self._onDeleteRequestCompleted(reply, ca, error) HttpRequestManager.getInstance().delete(url="{}/{}".format( self.BACKUP_URL, backup_id), callback=finishedCallback, error_callback=errorCallback, scope=self._json_cloud_scope)
def subscribeUserToPackage(self, package_id: str, sdk_version: str) -> None: """Subscribe the user (if logged in) to the package for a given SDK :param package_id: the package identification string :param sdk_version: the SDK version """ if self._account.isLoggedIn: HttpRequestManager.getInstance().put(url=USER_PACKAGES_URL, data=json.dumps({ "data": { "package_id": package_id, "sdk_version": sdk_version } }).encode(), scope=self._scope)
def _requestUploadSlot(self, backup_metadata: Dict[str, Any], backup_size: int) -> None: """Request a backup upload slot from the API. :param backup_metadata: A dict containing some meta data about the backup. :param backup_size: The size of the backup file in bytes. """ payload = json.dumps({"data": {"backup_size": backup_size, "metadata": backup_metadata } }).encode() HttpRequestManager.getInstance().put( self._api_backup_url, data = payload, callback = self._onUploadSlotCompleted, error_callback = self._onUploadSlotCompleted, scope = self._json_cloud_scope)
def _openSelectedFile(self, temp_dir: str, project_name: str, file_name: str, download_url: str) -> None: """ Downloads, then opens, the single specified file. :param temp_dir: The already created temporary directory where the files will be stored. :param project_name: Name of the project the file belongs to (used for error reporting). :param file_name: Name of the file to be downloaded and opened (used for error reporting). :param download_url: This url will be downloaded, then the downloaded file will be opened in Cura. """ if not download_url: Logger.log("e", "No download url for file '{}'".format(file_name)) return progress_message = Message(text = "{0}/{1}".format(project_name, file_name), dismissable = False, lifetime = 0, progress = 0, title = "Downloading...") progress_message.setProgress(0) progress_message.show() def progressCallback(rx: int, rt: int) -> None: progress_message.setProgress(math.floor(rx * 100.0 / rt)) def finishedCallback(reply: QNetworkReply) -> None: progress_message.hide() try: with open(os.path.join(temp_dir, file_name), "wb+") as temp_file: bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) while bytes_read: temp_file.write(bytes_read) bytes_read = reply.read(self.DISK_WRITE_BUFFER_SIZE) CuraApplication.getInstance().processEvents() temp_file_name = temp_file.name except IOError as ex: Logger.logException("e", "Can't write Digital Library file {0}/{1} download to temp-directory {2}.", ex, project_name, file_name, temp_dir) Message( text = "Failed to write to temporary file for '{}'.".format(file_name), title = "File-system error", lifetime = 10 ).show() return CuraApplication.getInstance().readLocalFile( QUrl.fromLocalFile(temp_file_name), add_to_recent_files = False) def errorCallback(reply: QNetworkReply, error: QNetworkReply.NetworkError, p = project_name, f = file_name) -> None: progress_message.hide() Logger.error("An error {0} {1} occurred while downloading {2}/{3}".format(str(error), str(reply), p, f)) Message( text = "Failed Digital Library download for '{}'.".format(f), title = "Network error {}".format(error), lifetime = 10 ).show() download_manager = HttpRequestManager.getInstance() download_manager.get(download_url, callback = finishedCallback, download_progress_callback = progressCallback, error_callback = errorCallback, scope = UltimakerCloudScope(CuraApplication.getInstance()))
def checkForUpdates(self, packages: List[Dict[str, Any]]) -> None: installed_packages = "&".join([ f"installed_packages={package['package_id']}:{package['package_version']}" for package in packages ]) request_url = f"{PACKAGE_UPDATES_URL}?{installed_packages}" self._ongoing_requests[ "check_updates"] = HttpRequestManager.getInstance().get( request_url, scope=self._scope, callback=self._parseResponse)
def __init__(self, tray_icon_name: str = None, **kwargs) -> None: plugin_path = "" if sys.platform == "win32": if hasattr(sys, "frozen"): plugin_path = os.path.join( os.path.dirname(os.path.abspath(sys.executable)), "PyQt5", "plugins") Logger.log("i", "Adding QT5 plugin path: %s", plugin_path) QCoreApplication.addLibraryPath(plugin_path) else: import site for sitepackage_dir in site.getsitepackages(): QCoreApplication.addLibraryPath( os.path.join(sitepackage_dir, "PyQt5", "plugins")) elif sys.platform == "darwin": plugin_path = os.path.join(self.getInstallPrefix(), "Resources", "plugins") if plugin_path: Logger.log("i", "Adding QT5 plugin path: %s", plugin_path) QCoreApplication.addLibraryPath(plugin_path) # use Qt Quick Scene Graph "basic" render loop os.environ["QSG_RENDER_LOOP"] = "basic" super().__init__(sys.argv, **kwargs) # type: ignore self._qml_import_paths = [] #type: List[str] self._main_qml = "main.qml" #type: str self._qml_engine = None #type: Optional[QQmlApplicationEngine] self._main_window = None #type: Optional[MainWindow] self._tray_icon_name = tray_icon_name #type: Optional[str] self._tray_icon = None #type: Optional[str] self._tray_icon_widget = None #type: Optional[QSystemTrayIcon] self._theme = None #type: Optional[Theme] self._renderer = None #type: Optional[QtRenderer] self._job_queue = None #type: Optional[JobQueue] self._version_upgrade_manager = None #type: Optional[VersionUpgradeManager] self._is_shutting_down = False #type: bool self._recent_files = [] #type: List[QUrl] self._configuration_error_message = None #type: Optional[ConfigurationErrorMessage] self._http_network_request_manager = HttpRequestManager(parent=self) #Metadata required for the file dialogues. self.setOrganizationDomain("https://ultimaker.com/") self.setOrganizationName("Ultimaker B.V.")
def updatePackages(self) -> None: """ Make a request for the first paginated page of packages. When the request is done, the list will get updated with the new package models. """ self.setErrorMessage("") # Clear any previous errors. self.setIsLoading(True) self._ongoing_requests["get_packages"] = HttpRequestManager.getInstance().get( self._request_url, scope = self._scope, callback = self._parseResponse, error_callback = self._onError )
def __init__(self, app: CuraApplication, on_error: Callable[[List[CloudError]], None]) -> None: """Initializes a new cloud API client. :param app: :param account: The user's account object :param on_error: The callback to be called whenever we receive errors from the server. """ super().__init__() self._app = app self._account = app.getCuraAPI().account self._scope = JsonDecoratorScope(UltimakerCloudScope(app)) self._http = HttpRequestManager.getInstance() self._on_error = on_error self._upload = None # type: Optional[ToolPathUploader]
def __init__(self, parent: Optional["QObject"] = None): super().__init__(parent) self._http = HttpRequestManager.getInstance() self._statuses = { self.ULTIMAKER_CLOUD_STATUS_URL: True, "http://example.com": True } # Create a timer for automatic updates self._update_timer = QTimer() self._update_timer.setInterval(int(self.UPDATE_INTERVAL * 1000)) # The timer is restarted automatically self._update_timer.setSingleShot(False) self._update_timer.timeout.connect(self._update) self._update_timer.start()
def callback( reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if error is not None: Logger.log("w", "Could not get backups: " + str(error)) changed([]) return backup_list_response = HttpRequestManager.readJSON(reply) if "data" not in backup_list_response: Logger.log( "w", "Could not get backups from remote, actual response body was: %s", str(backup_list_response)) changed([]) # empty list of backups return changed(backup_list_response["data"])
def _onUploadSlotCompleted( self, reply: QNetworkReply, error: Optional["QNetworkReply.NetworkError"] = None) -> None: if HttpRequestManager.safeHttpStatus(reply) >= 300: replyText = HttpRequestManager.readText(reply) Logger.warning("Could not request backup upload: %s", replyText) self.backup_upload_error_message = self.DEFAULT_UPLOAD_ERROR_MESSAGE if HttpRequestManager.safeHttpStatus(reply) == 400: errors = json.loads(replyText)["errors"] if "moreThanMaximum" in [ error["code"] for error in errors if error["meta"] and error["meta"]["field_name"] == "backup_size" ]: if self._backup_zip is None: # will never happen; keep mypy happy zip_error = "backup is None." else: zip_error = "{} exceeds max size.".format( str(len(self._backup_zip))) sentry_sdk.capture_message( "backup failed: {}".format(zip_error), level="warning") self.backup_upload_error_message = catalog.i18nc( "@error:file_size", "The backup exceeds the maximum file size.") from sentry_sdk import capture_message self._job_done.set() return if error is not None: Logger.warning("Could not request backup upload: %s", HttpRequestManager.qt_network_error_name(error)) self.backup_upload_error_message = self.DEFAULT_UPLOAD_ERROR_MESSAGE self._job_done.set() return backup_upload_url = HttpRequestManager.readJSON( reply)["data"]["upload_url"] # Upload the backup to storage. HttpRequestManager.getInstance().put( backup_upload_url, data=self._backup_zip, callback=self._uploadFinishedCallback, error_callback=self._uploadFinishedCallback)
def checkNewVersion(self, silent=False, display_same_version=True) -> None: """Connect with software.ultimaker.com, load latest.json and check version info. If the version info is higher then the current version, spawn a message to allow the user to download it. :param silent: Suppresses messages other than "new version found" messages. This is used when checking for a new version at startup. :param display_same_version: Whether to display the same update message twice (True) or suppress the update message if the user has already seen the update for a particular version. When manually checking for updates, the user wants to display the update even if he's already seen it. """ http_manager = HttpRequestManager.getInstance() Logger.log("i", "Checking for new version") http_manager.get(self.url, callback=lambda reply: self._onRequestCompleted( reply, silent, display_same_version)) self._download_url = None