File: OutputDeviceManager.py

package info (click to toggle)
uranium 5.0.0-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,304 kB
  • sloc: python: 31,765; sh: 132; makefile: 12
file content (395 lines) | stat: -rw-r--r-- 15,153 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# Copyright (c) 2019 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.
from enum import Enum
from typing import Dict, Optional, TYPE_CHECKING, ValuesView, KeysView

from UM.Signal import Signal, signalemitter
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry


if TYPE_CHECKING:
    from UM.OutputDevice.OutputDevice import OutputDevice
    from UM.OutputDevice.ProjectOutputDevice import ProjectOutputDevice
    from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin


class ManualDeviceAdditionAttempt(Enum):
    """
    Used internally to determine plugins capable of 'manual' addition of devices, see also [add|remove]ManualDevice below.
    """
    NO = 0,        # The plugin can't add a device 'manually' (or at least not with the given parameters).
    POSSIBLE = 1,  # The plugin will try to add the (specified) device 'manually', unless another plugin has priority.
    PRIORITY = 2   # The plugin has determined by the specified parameters that it's responsible for adding this device
                   #     and thus has priority. If this fails, the plugins that replied 'POSSIBLE' will be tried.
                   #     NOTE: This last value should be used with great care!


@signalemitter
class OutputDeviceManager:
    """Manages all available output devices and the plugin objects used to create them.

    This class is intended as the main entry point for anything relating to file saving.
    For the most basic usage, call getActiveDevice() to get an output device, then
    call OutputDevice::requestWrite() on the returned object.

    Active Device
    -------------

    The active device by default is determined based on the priority of individual
    OutputDevice instances when there is more than one OutputDevice available. When
    adding a device, the active device will be updated with the highest priority device.
    Should there be two devices with the same priority the active device will be the first
    device encountered with that priority.

    Calling setActiveDevice() will override this behaviour and instead force the active
    device to the specified device. This active device will not change when a new device
    is added or removed, but it will revert back to the default behaviour if the active
    device is removed. Call resetActiveDevice() to reset the active device to the default
    behaviour based on priority.

    OutputDevicePlugin and OutputDevice creation/removal
    ----------------------------------------------------

    Each instance of an OutputDevicePlugin is meant as an OutputDevice creation object.
    Subclasses of OutputDevicePlugin are meant to perform device lookup and listening
    for events like device hot-plugging. When a new device has been detected, the plugin
    class should create an instance of an OutputDevice subclass and add it to this
    manager class using addOutputDevice(). Similarly, if a device has been removed the
    OutputDevicePlugin is expected to call removeOutputDevice() to remove the proper
    device.

    """

    def __init__(self) -> None:
        super().__init__()

        self._output_devices = {}  # type: Dict[str, OutputDevice]
        self._project_output_devices = {}  # type: Dict[str, ProjectOutputDevice]
        self._plugins = {}  # type: Dict[str, OutputDevicePlugin]
        self._active_device = None  # type: Optional[OutputDevice]
        self._active_device_override = False
        self._write_in_progress = False
        PluginRegistry.addType("output_device", self.addOutputDevicePlugin)

        self._is_running = False

    writeStarted = Signal()
    """Emitted whenever a registered device emits writeStarted.

    :sa OutputDevice::writeStarted
    """

    writeProgress = Signal()
    """Emitted whenever a registered device emits writeProgress.

    :sa OutputDevice::writeProgress
    """

    writeFinished = Signal()
    """Emitted whenever a registered device emits writeFinished.

    :sa OutputDevice::writeFinished
    """

    writeError = Signal()
    """Emitted whenever a registered device emits writeError.

    :sa OutputDevice::writeError
    """

    writeSuccess = Signal()
    """Emitted whenever a registered device emits writeSuccess.

    :sa OutputDevice::writeSuccess
    """

    manualDeviceAdded = Signal()
    """Emitted whenever a device has been added manually."""

    manualDeviceRemoved = Signal()
    """Emitted whenever a device has been removed manually."""

    outputDevicesChanged = Signal()
    """Emitted whenever an output device is added or removed."""

    projectOutputDevicesChanged = Signal()
    """Emitted whenever an output device that can handle project files is added or removed."""

    activeDeviceChanged = Signal()
    """Emitted whenever the active device changes."""

    def getOutputDevices(self) -> ValuesView["OutputDevice"]:
        """Get a list of all registered output devices.

        :return: :type{list} A list of all registered output devices.
        """

        return self._output_devices.values()

    def getProjectOutputDevices(self) -> ValuesView["ProjectOutputDevice"]:
        """Get a list of all registered output devices.

        :return: :type{list} A list of all registered output devices.
        """

        return self._project_output_devices.values()

    def getOutputDeviceIds(self) -> KeysView[str]:
        """Get a list of all IDs of registered output devices.

        :return: :type{list} A list of all registered output device ids.
        """

        return self._output_devices.keys()

    def getOutputDevice(self, device_id: str) -> Optional["OutputDevice"]:
        """Get an output device by ID.

        :param device_id: The ID of the device to retrieve.
        :return: :type{OutputDevice} The output device corresponding to the ID or None if not found.
        """

        return self._output_devices.get(device_id, self._project_output_devices.get(device_id, None))

    def start(self) -> None:
        for plugin_id, plugin in self._plugins.items():
            try:
                plugin.start()
            except Exception:
                Logger.logException("e", "Exception starting OutputDevicePlugin %s", plugin.getPluginId())

    def stop(self) -> None:
        for plugin_id, plugin in self._plugins.items():
            try:
                plugin.stop()
            except Exception:
                Logger.logException("e", "Exception starting OutputDevicePlugin %s", plugin.getPluginId())

    def startDiscovery(self) -> None:
        for plugin_id, plugin in self._plugins.items():
            try:
                plugin.startDiscovery()
            except Exception:
                Logger.logException("e", "Exception startDiscovery OutputDevicePlugin %s", plugin.getPluginId())

    def refreshConnections(self) -> None:
        for plugin_id, plugin in self._plugins.items():
            try:
                plugin.refreshConnections()
            except Exception:
                Logger.logException("e", "Exception refreshConnections OutputDevicePlugin %s", plugin.getPluginId())

    def connectWriteSignalsToDevice(self, device: "OutputDevice") -> None:
        """
        Connects all the necessary write signals of the device to the write signals of the manager
        :param device: The output device
        """
        device.writeStarted.connect(self.writeStarted)
        device.writeProgress.connect(self.writeProgress)
        device.writeFinished.connect(self.writeFinished)
        device.writeError.connect(self.writeError)
        device.writeSuccess.connect(self.writeSuccess)

    def disconnectWriteSignalsFromDevice(self, device: "OutputDevice") -> None:
        """
        Disconnects all the necessary write signals of the device from the write signals of the manager
        :param device: The output device
        """
        device.writeStarted.disconnect(self.writeStarted)
        device.writeProgress.disconnect(self.writeProgress)
        device.writeFinished.disconnect(self.writeFinished)
        device.writeError.disconnect(self.writeError)
        device.writeSuccess.disconnect(self.writeSuccess)

    def addOutputDevice(self, device: "OutputDevice") -> None:
        """Add and register an output device.

        :param :type{OutputDevice} The output device to add.

        :note Does nothing if a device with the same ID as the passed device was already added.
        """

        if device.getId() in self._output_devices:
            Logger.log("i", "Output Device %s already added", device.getId())
            return

        self._output_devices[device.getId()] = device
        self.connectWriteSignalsToDevice(device)
        self.outputDevicesChanged.emit()

        if not self._active_device or not self._active_device_override:
            self._active_device = self._findHighestPriorityDevice()
            self.activeDeviceChanged.emit()

    def removeOutputDevice(self, device_id: str) -> bool:
        """Remove a registered device by ID

        :param device_id: The ID of the device to remove.

        :note This does nothing if the device_id does not correspond to a registered device.
        :return: Whether the device was successfully removed or not.
        """

        if device_id not in self._output_devices:
            Logger.log("w", "Could not find output device with id %s to remove", device_id)
            return False

        device = self._output_devices[device_id]

        del self._output_devices[device_id]

        self.disconnectWriteSignalsFromDevice(device)
        self.outputDevicesChanged.emit()

        if self._active_device is not None and self._active_device.getId() == device_id:
            self._write_in_progress = False
            self.resetActiveDevice()
        return True

    def addProjectOutputDevice(self, device: "ProjectOutputDevice") -> None:
        """Add and register a project output device.

        :param device: The output device to add.

        :note Does nothing if a device with the same ID as the passed device was already added.
        """

        if device.getId() in self._project_output_devices:
            Logger.log("i", "Project Output Device %s already added", device.getId())
            return

        self._project_output_devices[device.getId()] = device
        device.enabledChanged.connect(self.projectOutputDevicesChanged.emit)

        if device.enabled:
            if device.add_to_output_devices:
                self.addOutputDevice(device)
            else:
                # Call the connectWriteSignalsToDevice(..) only if addOutputDevice(..) function hasn't been called already
                # to avoid connecting the signals twice
                self.connectWriteSignalsToDevice(device)

        self.projectOutputDevicesChanged.emit()

    def removeProjectOutputDevice(self, device_id: str) -> bool:
        """Remove a registered project output device by ID

        :param device_id: The ID of the device to remove.

        :note This does nothing if the device_id does not correspond to a registered device.
        :return: Whether the device was successfully removed or not.
        """

        if device_id not in self._project_output_devices and device_id not in self._output_devices:
            Logger.log("w", "Could not find output device with id %s to remove", device_id)
            return False

        device = self._project_output_devices[device_id]
        del self._project_output_devices[device_id]

        self.disconnectWriteSignalsFromDevice(device)
        self.projectOutputDevicesChanged.emit()

        if device_id in self._output_devices:
            del self._output_devices[device_id]
            self.outputDevicesChanged.emit()

            if self._active_device is not None and self._active_device.getId() == device_id:
                self._write_in_progress = False
                self.resetActiveDevice()
        return True

    def getActiveDevice(self):
        """Get the active device."""

        return self._active_device

    def setActiveDevice(self, device_id: str) -> None:
        """Set the active device.

        :param device_id: The ID of the device to set as active device.

        :note This does nothing if the device_id does not correspond to a registered device.
        :note This will override the default active device selection behaviour.
        """

        if device_id not in self._output_devices:
            return

        if not self._active_device or self._active_device.getId() != device_id:
            self._active_device = self.getOutputDevice(device_id)
            self._write_in_progress = False
            self._active_device_override = True
            self.activeDeviceChanged.emit()

    def resetActiveDevice(self) -> None:
        """Reset the active device to the default device."""

        self._active_device = self._findHighestPriorityDevice()
        self._active_device_override = False
        self._write_in_progress = False
        self.activeDeviceChanged.emit()

    def addOutputDevicePlugin(self, plugin: "OutputDevicePlugin") -> None:
        """Add an OutputDevicePlugin instance.

        :param :type{OutputDevicePlugin} The plugin to add.

        :note This does nothing if the plugin was already added.
        """

        if plugin.getPluginId() in self._plugins:
            Logger.log("i", "Output Device Plugin %s was already added.", plugin.getPluginId())
            return

        self._plugins[plugin.getPluginId()] = plugin
        if self._is_running:
            try:
                plugin.start()
            except Exception:
                Logger.logException("e", "Exception starting OutputDevicePlugin %s", plugin.getPluginId())

    def removeOutputDevicePlugin(self, plugin_id: str) -> None:
        """Remove an OutputDevicePlugin by ID.

        :param plugin_id: The ID of the plugin to remove.

        :note This does nothing if the specified plugin_id was not found.
        """

        Logger.log("d", "Remove output device plugin %s", plugin_id)
        if plugin_id not in self._plugins:
            return

        try:
            self._plugins[plugin_id].stop()
        except Exception as e:
            Logger.log("e", "Exception stopping plugin %s: %s", plugin_id, repr(e))

        del self._plugins[plugin_id]

    def getAllOutputDevicePlugins(self) -> Dict[str, "OutputDevicePlugin"]:
        return self._plugins

    def getOutputDevicePlugin(self, plugin_id: str) -> Optional["OutputDevicePlugin"]:
        """Get an OutputDevicePlugin by plugin ID

        :param plugin_id: The ID of the plugin to retrieve

        :return: The plugin corresponding to the specified ID or None if it was not found.
        """

        return self._plugins.get(plugin_id, None)

    def _findHighestPriorityDevice(self) -> Optional["OutputDevice"]:
        """private:

        """

        device = None
        for key, value in self._output_devices.items():
            if not device or value.getPriority() > device.getPriority():
                device = value

        return device