mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-05-24 23:29:00 +08:00
132 lines
6.0 KiB
Python
132 lines
6.0 KiB
Python
# Copyright (c) 2018 Ultimaker B.V.
|
|
# Cura is released under the terms of the LGPLv3 or higher.
|
|
from typing import Dict, List, Optional
|
|
|
|
from PyQt5.QtCore import QTimer
|
|
|
|
from UM import i18nCatalog
|
|
from UM.Logger import Logger
|
|
from UM.Message import Message
|
|
from cura.CuraApplication import CuraApplication
|
|
from plugins.UM3NetworkPrinting.src.Cloud.CloudApiClient import CloudApiClient
|
|
from .CloudOutputDevice import CloudOutputDevice
|
|
from .Models import CloudCluster, CloudErrorObject
|
|
|
|
|
|
## The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters.
|
|
# Keeping all cloud related logic in this class instead of the UM3OutputDevicePlugin results in more readable code.
|
|
#
|
|
# API spec is available on https://api.ultimaker.com/docs/connect/spec/.
|
|
#
|
|
class CloudOutputDeviceManager:
|
|
|
|
# The interval with which the remote clusters are checked
|
|
CHECK_CLUSTER_INTERVAL = 5.0 # seconds
|
|
|
|
# The translation catalog for this device.
|
|
I18N_CATALOG = i18nCatalog("cura")
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
# Persistent dict containing the remote clusters for the authenticated user.
|
|
self._remote_clusters = {} # type: Dict[str, CloudOutputDevice]
|
|
|
|
application = CuraApplication.getInstance()
|
|
self._output_device_manager = application.getOutputDeviceManager()
|
|
|
|
self._account = application.getCuraAPI().account
|
|
self._account.loginStateChanged.connect(self._getRemoteClusters)
|
|
self._api = CloudApiClient(self._account, self._onApiError)
|
|
|
|
# When switching machines we check if we have to activate a remote cluster.
|
|
application.globalContainerStackChanged.connect(self._connectToActiveMachine)
|
|
|
|
self.update_timer = QTimer(CuraApplication.getInstance())
|
|
self.update_timer.setInterval(self.CHECK_CLUSTER_INTERVAL * 1000)
|
|
self.update_timer.setSingleShot(False)
|
|
self.update_timer.timeout.connect(self._getRemoteClusters)
|
|
|
|
## Gets all remote clusters from the API.
|
|
def _getRemoteClusters(self) -> None:
|
|
Logger.log("i", "Retrieving remote clusters")
|
|
if self._account.isLoggedIn:
|
|
self._api.getClusters(self._onGetRemoteClustersFinished)
|
|
|
|
# Only start the polling timer after the user is authenticated
|
|
# The first call to _getRemoteClusters comes from self._account.loginStateChanged
|
|
if not self.update_timer.isActive():
|
|
self.update_timer.start()
|
|
|
|
## Callback for when the request for getting the clusters. is finished.
|
|
def _onGetRemoteClustersFinished(self, clusters: List[CloudCluster]) -> None:
|
|
found_clusters = {c.cluster_id: c for c in clusters}
|
|
|
|
Logger.log("i", "Parsed remote clusters to %s", found_clusters)
|
|
if not found_clusters:
|
|
return
|
|
|
|
known_cluster_ids = set(self._remote_clusters.keys())
|
|
found_cluster_ids = set(found_clusters.keys())
|
|
|
|
# Add an output device for each new remote cluster.
|
|
# We only add when is_online as we don't want the option in the drop down if the cluster is not online.
|
|
for cluster_id in found_cluster_ids.difference(known_cluster_ids):
|
|
if found_clusters[cluster_id].is_online:
|
|
self._addCloudOutputDevice(found_clusters[cluster_id])
|
|
|
|
# Remove output devices that are gone
|
|
for cluster_id in known_cluster_ids.difference(found_cluster_ids):
|
|
self._removeCloudOutputDevice(found_clusters[cluster_id])
|
|
|
|
## Adds a CloudOutputDevice for each entry in the remote cluster list from the API.
|
|
# \param cluster: The cluster that was added.
|
|
def _addCloudOutputDevice(self, cluster: CloudCluster):
|
|
device = CloudOutputDevice(self._api, cluster.cluster_id)
|
|
self._output_device_manager.addOutputDevice(device)
|
|
self._remote_clusters[cluster.cluster_id] = device
|
|
device.connect() # TODO: remove this
|
|
self._connectToActiveMachine(cluster.cluster_id)
|
|
|
|
## Remove a CloudOutputDevice
|
|
# \param cluster: The cluster that was removed
|
|
def _removeCloudOutputDevice(self, cluster: CloudCluster):
|
|
self._output_device_manager.removeOutputDevice(cluster.cluster_id)
|
|
if cluster.cluster_id in self._remote_clusters:
|
|
del self._remote_clusters[cluster.cluster_id]
|
|
|
|
## Callback for when the active machine was changed by the user.
|
|
def _connectToActiveMachine(self, cluster_id: Optional[str] = None) -> None:
|
|
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
|
|
if not active_machine:
|
|
return
|
|
|
|
# TODO: Remove this once correct pairing has been added (see below).
|
|
if cluster_id:
|
|
active_machine.setMetaDataEntry("um_cloud_cluster_id", cluster_id)
|
|
|
|
# Check if the stored cluster_id for the active machine is in our list of remote clusters.
|
|
stored_cluster_id = active_machine.getMetaDataEntry("um_cloud_cluster_id")
|
|
if stored_cluster_id in self._remote_clusters.keys():
|
|
self._remote_clusters.get(stored_cluster_id).connect()
|
|
return
|
|
|
|
# TODO: See if this cloud cluster still has to be associated to the active machine.
|
|
# TODO: We have to get a common piece of data, like local network hostname, from the active machine and
|
|
# TODO: cloud cluster and then set the "um_cloud_cluster_id" meta data key on the active machine.
|
|
# TODO: If so, we can also immediate connect to it.
|
|
# active_machine.setMetaDataEntry("um_cloud_cluster_id", "")
|
|
# self._remote_clusters.get(stored_cluster_id).connect()
|
|
|
|
## Handles an API error received from the cloud.
|
|
# \param errors: The errors received
|
|
def _onApiError(self, errors: List[CloudErrorObject]) -> None:
|
|
message = ". ".join(e.title for e in errors) # TODO: translate errors
|
|
message = Message(
|
|
text = message,
|
|
title = self.I18N_CATALOG.i18nc("@info:title", "Error"),
|
|
lifetime = 10,
|
|
dismissable = True
|
|
)
|
|
message.show()
|