Sun, 16 Mar 2025 12:53:12 +0100
Added the Adafruit Feather nRF52840 to the list of known NRF52 boards and changed the list of known CircuitPython boards to be more explicit with respect to Adafruit boards (i.e. VID 0x239A).
9740 | 1 | # -*- coding: utf-8 -*- |
2 | ||
11090
f5f5f5803935
Updated copyright for 2025.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11011
diff
changeset
|
3 | # Copyright (c) 2023 - 2025 Detlev Offenbach <detlev@die-offenbachs.de> |
9740 | 4 | # |
5 | ||
6 | """ | |
7 | Module implementing an interface to the 'circup' package. | |
8 | """ | |
9 | ||
10 | import importlib | |
11 | import logging | |
12 | import os | |
13 | import re | |
14 | import shutil | |
15 | ||
16 | import requests | |
17 | ||
18 | from PyQt6.QtCore import QObject, pyqtSlot | |
19 | from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit, QMenu | |
20 | ||
21 | from eric7 import Preferences | |
22 | from eric7.EricGui.EricOverrideCursor import EricOverrideCursor | |
23 | from eric7.EricWidgets import EricFileDialog, EricMessageBox | |
24 | from eric7.EricWidgets.EricApplication import ericApp | |
25 | from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog | |
26 | from eric7.SystemUtilities import PythonUtilities | |
27 | ||
28 | try: | |
29 | import circup | |
30 | ||
31 | circup.logger.setLevel(logging.WARNING) | |
32 | except ImportError: | |
33 | circup = None | |
34 | ||
35 | ||
36 | class CircuitPythonUpdaterInterface(QObject): | |
37 | """ | |
38 | Class implementing an interface to the 'circup' package. | |
39 | """ | |
40 | ||
41 | def __init__(self, device, parent=None): | |
42 | """ | |
43 | Constructor | |
44 | ||
45 | @param device reference to the CircuitPython device interface | |
46 | @type CircuitPythonDevice | |
47 | @param parent reference to the parent object (defaults to None) | |
48 | @type QObject (optional) | |
49 | """ | |
50 | super().__init__(parent) | |
51 | ||
52 | self.__device = device | |
53 | ||
54 | self.__installMenu = QMenu(self.tr("Install Modules")) | |
55 | self.__installMenu.setTearOffEnabled(True) | |
56 | self.__installMenu.addAction( | |
57 | self.tr("Select from Available Modules"), self.__installFromAvailable | |
58 | ) | |
59 | self.__installMenu.addAction( | |
60 | self.tr("Install Requirements"), self.__installRequirements | |
61 | ) | |
62 | self.__installMenu.addAction( | |
63 | self.tr("Install based on 'code.py'"), self.__installFromCode | |
64 | ) | |
65 | self.__installMenu.addSeparator() | |
66 | self.__installPyAct = self.__installMenu.addAction( | |
67 | self.tr("Install Python Source") | |
68 | ) | |
69 | self.__installPyAct.setCheckable(True) | |
70 | self.__installPyAct.setChecked(False) | |
71 | # kind of hack to make this action not hide the menu | |
72 | # Note: parent menus are hidden nevertheless | |
73 | self.__installPyAct.toggled.connect(self.__installMenu.show) | |
74 | ||
75 | def populateMenu(self, menu): | |
76 | """ | |
77 | Public method to populate the 'circup' menu. | |
78 | ||
79 | @param menu reference to the menu to be populated | |
80 | @type QMenu | |
81 | """ | |
82 | from .CircupFunctions import patch_circup | |
83 | ||
84 | patch_circup() | |
9743
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
85 | isMounted = self.__device.supportsLocalFileAccess() |
9740 | 86 | |
87 | act = menu.addAction(self.tr("circup"), self.__aboutCircup) | |
88 | font = act.font() | |
89 | font.setBold(True) | |
90 | act.setFont(font) | |
91 | menu.addSeparator() | |
9743
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
92 | menu.addAction( |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
93 | self.tr("List Outdated Modules"), self.__listOutdatedModules |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
94 | ).setEnabled(isMounted) |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
95 | menu.addAction(self.tr("Update Modules"), self.__updateModules).setEnabled( |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
96 | isMounted |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
97 | ) |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
98 | menu.addAction( |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
99 | self.tr("Update All Modules"), self.__updateAllModules |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
100 | ).setEnabled(isMounted) |
9740 | 101 | menu.addSeparator() |
102 | menu.addAction(self.tr("Show Available Modules"), self.__showAvailableModules) | |
9743
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
103 | menu.addAction( |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
104 | self.tr("Show Installed Modules"), self.__showInstalledModules |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
105 | ).setEnabled(isMounted) |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
106 | menu.addMenu(self.__installMenu).setEnabled(isMounted) |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
107 | menu.addAction( |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
108 | self.tr("Uninstall Modules"), self.__uninstallModules |
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
109 | ).setEnabled(isMounted) |
9740 | 110 | menu.addSeparator() |
111 | menu.addAction( | |
112 | self.tr("Generate Requirements ..."), self.__generateRequirements | |
9743
741c61c2cfca
Changed the enabled status of some CircuitPython library management menu entries depending on the mounted state of the device.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9741
diff
changeset
|
113 | ).setEnabled(isMounted) |
9740 | 114 | menu.addSeparator() |
115 | menu.addAction(self.tr("Show Bundles"), self.__showBundles) | |
116 | menu.addAction(self.tr("Show Bundles with Modules"), self.__showBundlesModules) | |
117 | menu.addSeparator() | |
118 | menu.addAction(self.tr("Add Bundle"), self.__addBundle) | |
119 | menu.addAction(self.tr("Remove Bundles"), self.__removeBundle) | |
9869
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
120 | menu.addSeparator() |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
121 | menu.addAction(self.tr("Show Local Cache Path"), self.__showCachePath) |
9740 | 122 | |
123 | @pyqtSlot() | |
124 | def __aboutCircup(self): | |
125 | """ | |
126 | Private slot to show some info about 'circup'. | |
127 | """ | |
128 | version = circup.get_circup_version() | |
129 | if version is None: | |
130 | version = self.tr("unknown") | |
131 | ||
132 | EricMessageBox.information( | |
133 | None, | |
134 | self.tr("About circup"), | |
135 | self.tr( | |
136 | """<p><b>circup Version {0}</b></p>""" | |
137 | """<p><i>circup</i> is a tool to manage and update libraries on a""" | |
138 | """ CircuitPython device.</p>""", | |
139 | ).format(version), | |
140 | ) | |
141 | ||
142 | @pyqtSlot() | |
143 | def installCircup(self): | |
144 | """ | |
145 | Public slot to install the 'circup' package via pip. | |
146 | """ | |
147 | global circup | |
148 | ||
149 | pip = ericApp().getObject("Pip") | |
150 | pip.installPackages( | |
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
151 | ["circup>=2.0.0"], interpreter=PythonUtilities.getPythonExecutable() |
9740 | 152 | ) |
153 | ||
154 | circup = importlib.import_module("circup") | |
155 | circup.logger.setLevel(logging.WARNING) | |
156 | ||
157 | @pyqtSlot() | |
158 | def __showBundles(self, withModules=False): | |
159 | """ | |
160 | Private slot to show the available bundles (default and local). | |
161 | ||
162 | @param withModules flag indicating to list the modules and their version | |
163 | (defaults to False) | |
164 | @type bool (optional) | |
165 | """ | |
166 | from .ShowBundlesDialog import ShowBundlesDialog | |
167 | ||
168 | with EricOverrideCursor(): | |
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
169 | dlg = ShowBundlesDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
170 | withModules=withModules, parent=self.__device.microPython |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
171 | ) |
9740 | 172 | dlg.exec() |
173 | ||
174 | @pyqtSlot() | |
175 | def __showBundlesModules(self): | |
176 | """ | |
177 | Private slot to show the available bundles (default and local) with their | |
178 | modules. | |
179 | """ | |
180 | self.__showBundles(withModules=True) | |
181 | ||
182 | @pyqtSlot() | |
183 | def __addBundle(self): | |
184 | """ | |
185 | Private slot to add a bundle to the local bundles list, by "user/repo" github | |
186 | string. | |
187 | """ | |
188 | bundle, ok = QInputDialog.getText( | |
189 | None, | |
190 | self.tr("Add Bundle"), | |
191 | self.tr("Enter Bundle by 'User/Repo' Github String:"), | |
192 | QLineEdit.EchoMode.Normal, | |
193 | ) | |
194 | if ok and bundle: | |
195 | bundles = circup.get_bundles_local_dict() | |
196 | modified = False | |
197 | ||
198 | # do some cleanup | |
199 | bundle = re.sub(r"https?://github.com/([^/]+/[^/]+)(/.*)?", r"\1", bundle) | |
200 | if bundle in bundles: | |
201 | EricMessageBox.information( | |
202 | None, | |
203 | self.tr("Add Bundle"), | |
204 | self.tr( | |
205 | """<p>The bundle <b>{0}</b> is already in the list.</p>""" | |
206 | ).format(bundle), | |
207 | ) | |
208 | return | |
209 | ||
210 | try: | |
211 | cBundle = circup.Bundle(bundle) | |
212 | except ValueError: | |
213 | EricMessageBox.critical( | |
214 | None, | |
215 | self.tr("Add Bundle"), | |
216 | self.tr( | |
217 | """<p>The bundle string is invalid, expecting github URL""" | |
218 | """ or 'user/repository' string.</p>""" | |
219 | ), | |
220 | ) | |
221 | return | |
222 | ||
10170
6cf1ee737d8f
Corrected some more code style issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9870
diff
changeset
|
223 | result = requests.head("https://github.com/" + bundle, timeout=30) |
9740 | 224 | if result.status_code == requests.codes.NOT_FOUND: |
225 | EricMessageBox.critical( | |
226 | None, | |
227 | self.tr("Add Bundle"), | |
228 | self.tr( | |
229 | """<p>The bundle string is invalid. The repository doesn't""" | |
230 | """ exist (error code 404).</p>""" | |
231 | ), | |
232 | ) | |
233 | return | |
234 | ||
235 | if not cBundle.validate(): | |
236 | EricMessageBox.critical( | |
237 | None, | |
238 | self.tr("Add Bundle"), | |
239 | self.tr( | |
240 | """<p>The bundle string is invalid. Is the repository a valid""" | |
9741
901caff48307
Updated translations.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9740
diff
changeset
|
241 | """ circup bundle?</p>""" |
9740 | 242 | ), |
243 | ) | |
244 | return | |
245 | ||
246 | # Use the bundle string as the dictionary key for uniqueness | |
247 | bundles[bundle] = bundle | |
248 | modified = True | |
249 | EricMessageBox.information( | |
250 | None, | |
251 | self.tr("Add Bundle"), | |
252 | self.tr("""<p>Added bundle <b>{0}</b> ({1}).</p>""").format( | |
253 | bundle, cBundle.url | |
254 | ), | |
255 | ) | |
256 | ||
257 | if modified: | |
258 | # save the bundles list | |
259 | circup.save_local_bundles(bundles) | |
260 | # update and get the new bundle for the first time | |
261 | circup.get_bundle_versions(circup.get_bundles_list()) | |
262 | ||
263 | @pyqtSlot() | |
264 | def __removeBundle(self): | |
265 | """ | |
266 | Private slot to remove one or more bundles from the local bundles list. | |
267 | """ | |
268 | localBundles = circup.get_bundles_local_dict() | |
269 | dlg = EricListSelectionDialog( | |
10373
093dcebe5ecb
Corrected some uses of dict.keys(), dict.values() and dict.items().
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10170
diff
changeset
|
270 | sorted(localBundles), |
9740 | 271 | title=self.tr("Remove Bundles"), |
272 | message=self.tr("Select the bundles to be removed:"), | |
273 | checkBoxSelection=True, | |
11006
a671918232f3
Modified modal dialog usage to always include a valid parent (needed for Wayland).
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11005
diff
changeset
|
274 | parent=self.__device.microPython, |
9740 | 275 | ) |
276 | modified = False | |
277 | if dlg.exec() == QDialog.DialogCode.Accepted: | |
278 | bundles = dlg.getSelection() | |
279 | for bundle in bundles: | |
280 | del localBundles[bundle] | |
281 | modified = True | |
282 | ||
283 | if modified: | |
284 | circup.save_local_bundles(localBundles) | |
285 | EricMessageBox.information( | |
286 | None, | |
287 | self.tr("Remove Bundles"), | |
288 | self.tr( | |
289 | """<p>These bundles were removed from the local bundles list.{0}""" | |
290 | """</p>""" | |
291 | ).format("""<ul><li>{0}</li></ul>""".format("</li><li>".join(bundles))), | |
292 | ) | |
293 | ||
294 | @pyqtSlot() | |
295 | def __listOutdatedModules(self): | |
296 | """ | |
297 | Private slot to list the outdated modules of the connected device. | |
298 | """ | |
299 | from .ShowOutdatedDialog import ShowOutdatedDialog | |
300 | ||
301 | devicePath = self.__device.getWorkspace() | |
302 | ||
10690
fab36645aa7d
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10621
diff
changeset
|
303 | cpyVersion, _board_id = circup.get_circuitpython_version(devicePath) |
9740 | 304 | circup.CPY_VERSION = cpyVersion |
305 | ||
306 | with EricOverrideCursor(): | |
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
307 | dlg = ShowOutdatedDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
308 | devicePath=devicePath, parent=self.__device.microPython |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
309 | ) |
9740 | 310 | dlg.exec() |
311 | ||
312 | @pyqtSlot() | |
313 | def __updateModules(self): | |
314 | """ | |
315 | Private slot to update the modules of the connected device. | |
316 | """ | |
317 | from .ShowOutdatedDialog import ShowOutdatedDialog | |
318 | ||
319 | devicePath = self.__device.getWorkspace() | |
320 | ||
10690
fab36645aa7d
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10621
diff
changeset
|
321 | cpyVersion, _board_id = circup.get_circuitpython_version(devicePath) |
9740 | 322 | circup.CPY_VERSION = cpyVersion |
323 | ||
324 | with EricOverrideCursor(): | |
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
325 | dlg = ShowOutdatedDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
326 | devicePath=devicePath, |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
327 | selectionMode=True, |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
328 | parent=self.__device.microPython, |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
329 | ) |
9740 | 330 | if dlg.exec() == QDialog.DialogCode.Accepted: |
331 | modules = dlg.getSelection() | |
332 | self.__doUpdateModules(modules) | |
333 | ||
334 | @pyqtSlot() | |
335 | def __updateAllModules(self): | |
336 | """ | |
337 | Private slot to update all modules of the connected device. | |
338 | """ | |
339 | devicePath = self.__device.getWorkspace() | |
340 | ||
10690
fab36645aa7d
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10621
diff
changeset
|
341 | cpyVersion, _board_id = circup.get_circuitpython_version(devicePath) |
9740 | 342 | circup.CPY_VERSION = cpyVersion |
343 | ||
344 | with EricOverrideCursor(): | |
345 | modules = [ | |
346 | m | |
347 | for m in circup.find_modules(devicePath, circup.get_bundles_list()) | |
348 | if m.outofdate | |
349 | ] | |
350 | if modules: | |
351 | self.__doUpdateModules(modules) | |
352 | else: | |
353 | EricMessageBox.information( | |
354 | None, | |
355 | self.tr("Update Modules"), | |
356 | self.tr("All modules are already up-to-date."), | |
357 | ) | |
358 | ||
359 | def __doUpdateModules(self, modules): | |
360 | """ | |
361 | Private method to perform the update of a list of modules. | |
362 | ||
363 | @param modules list of modules to be updated | |
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
364 | @type circup.module.Module |
9740 | 365 | """ |
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
366 | backend = circup.DiskBackend(self.__device.getWorkspace(), circup.logger) |
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
367 | |
9740 | 368 | updatedModules = [] |
369 | for module in modules: | |
370 | try: | |
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
371 | backend.update(module) |
9740 | 372 | updatedModules.append(module.name) |
373 | except Exception as ex: | |
374 | EricMessageBox.critical( | |
375 | None, | |
376 | self.tr("Update Modules"), | |
377 | self.tr( | |
378 | """<p>There was an error updating <b>{0}</b>.</p>""" | |
379 | """<p>Error: {1}</p>""" | |
380 | ).format(module.name, str(ex)), | |
381 | ) | |
382 | ||
383 | if updatedModules: | |
384 | EricMessageBox.information( | |
385 | None, | |
386 | self.tr("Update Modules"), | |
387 | self.tr( | |
388 | """<p>These modules were updated on the connected device.{0}</p>""" | |
389 | ).format( | |
390 | """<ul><li>{0}</li></ul>""".format("</li><li>".join(updatedModules)) | |
391 | ), | |
392 | ) | |
393 | else: | |
394 | EricMessageBox.information( | |
395 | None, | |
396 | self.tr("Update Modules"), | |
397 | self.tr("No modules could be updated."), | |
398 | ) | |
399 | ||
400 | @pyqtSlot() | |
401 | def __showAvailableModules(self): | |
402 | """ | |
403 | Private slot to show the available modules. | |
404 | ||
405 | These are modules which could be installed on the device. | |
406 | """ | |
9817 | 407 | from eric7.MicroPython.ShowModulesDialog import ShowModulesDialog |
9740 | 408 | |
409 | with EricOverrideCursor(): | |
9748 | 410 | availableModules = circup.get_bundle_versions(circup.get_bundles_list()) |
411 | moduleNames = [m.replace(".py", "") for m in availableModules] | |
412 | ||
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
413 | dlg = ShowModulesDialog(moduleNames, parent=self.__device.microPython) |
9740 | 414 | dlg.exec() |
415 | ||
416 | @pyqtSlot() | |
417 | def __showInstalledModules(self): | |
418 | """ | |
419 | Private slot to show the modules installed on the connected device. | |
420 | """ | |
421 | from .ShowInstalledDialog import ShowInstalledDialog | |
422 | ||
423 | devicePath = self.__device.getWorkspace() | |
424 | ||
425 | with EricOverrideCursor(): | |
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
426 | dlg = ShowInstalledDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
427 | devicePath=devicePath, parent=self.__device.microPython |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
428 | ) |
9740 | 429 | dlg.exec() |
430 | ||
431 | @pyqtSlot() | |
432 | def __installFromAvailable(self): | |
433 | """ | |
434 | Private slot to install modules onto the connected device. | |
435 | """ | |
9817 | 436 | from eric7.MicroPython.ShowModulesDialog import ShowModulesDialog |
9740 | 437 | |
438 | with EricOverrideCursor(): | |
9748 | 439 | availableModules = circup.get_bundle_versions(circup.get_bundles_list()) |
440 | moduleNames = [m.replace(".py", "") for m in availableModules] | |
441 | ||
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
442 | dlg = ShowModulesDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
443 | moduleNames, selectionMode=True, parent=self.__device.microPython |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
444 | ) |
9740 | 445 | if dlg.exec() == QDialog.DialogCode.Accepted: |
446 | modules = dlg.getSelection() | |
447 | self.__installModules(modules) | |
448 | ||
449 | @pyqtSlot() | |
450 | def __installRequirements(self): | |
451 | """ | |
452 | Private slot to install modules determined by a requirements file. | |
453 | """ | |
454 | homeDir = ( | |
455 | Preferences.getMicroPython("MpyWorkspace") | |
456 | or Preferences.getMultiProject("Workspace") | |
457 | or os.path.expanduser("~") | |
458 | ) | |
459 | reqFile = EricFileDialog.getOpenFileName( | |
460 | None, | |
461 | self.tr("Install Modules"), | |
462 | homeDir, | |
463 | self.tr("Text Files (*.txt);;All Files (*)"), | |
464 | ) | |
465 | if reqFile: | |
466 | if os.path.exists(reqFile): | |
467 | with open(reqFile, "r") as fp: | |
468 | requirementsText = fp.read() | |
469 | modules = circup.libraries_from_requirements(requirementsText) | |
470 | if modules: | |
471 | self.__installModules(modules) | |
472 | else: | |
473 | EricMessageBox.critical( | |
474 | None, | |
475 | self.tr("Install Modules"), | |
476 | self.tr( | |
477 | """<p>The given requirements file <b>{0}</b> does not""" | |
478 | """ contain valid modules.</p>""" | |
479 | ).format(reqFile), | |
480 | ) | |
481 | else: | |
482 | EricMessageBox.critical( | |
483 | None, | |
484 | self.tr("Install Modules"), | |
485 | self.tr( | |
486 | """<p>The given requirements file <b>{0}</b> does not exist.""" | |
487 | """</p>""" | |
488 | ).format(reqFile), | |
489 | ) | |
490 | ||
491 | @pyqtSlot() | |
492 | def __installFromCode(self): | |
493 | """ | |
494 | Private slot to install modules based on the 'code.py' file of the | |
495 | connected device. | |
496 | """ | |
497 | devicePath = self.__device.getWorkspace() | |
498 | ||
499 | codeFile = EricFileDialog.getOpenFileName( | |
500 | None, | |
501 | self.tr("Install Modules"), | |
502 | os.path.join(devicePath, "code.py"), | |
503 | self.tr("Python Files (*.py);;All Files (*)"), | |
504 | ) | |
505 | if codeFile: | |
506 | if os.path.exists(codeFile): | |
507 | with EricOverrideCursor(): | |
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
508 | availableModules = circup.command_utils.get_bundle_versions( |
9740 | 509 | circup.get_bundles_list() |
510 | ) | |
511 | moduleNames = {} | |
512 | for module, metadata in availableModules.items(): | |
513 | moduleNames[module.replace(".py", "")] = metadata | |
514 | ||
11011
f1f1c8d0255d
Modified the CircuitPython library management for circup >= 2.0.0.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11006
diff
changeset
|
515 | modules = circup.libraries_from_code_py(codeFile, moduleNames) |
9740 | 516 | if modules: |
517 | self.__installModules(modules) | |
518 | else: | |
519 | EricMessageBox.critical( | |
520 | None, | |
521 | self.tr("Install Modules"), | |
522 | self.tr( | |
523 | """<p>The given code file <b>{0}</b> does not""" | |
524 | """ contain valid import statements or does not import""" | |
525 | """ external modules.</p>""" | |
526 | ).format(codeFile), | |
527 | ) | |
528 | else: | |
529 | EricMessageBox.critical( | |
530 | None, | |
531 | self.tr("Install Modules"), | |
532 | self.tr( | |
533 | """<p>The given code file <b>{0}</b> does not exist.</p>""" | |
534 | ).format(codeFile), | |
535 | ) | |
536 | ||
537 | def __installModules(self, installs): | |
538 | """ | |
539 | Private method to install the given list of modules. | |
540 | ||
541 | @param installs list of module names to be installed | |
542 | @type list of str | |
543 | """ | |
544 | devicePath = self.__device.getWorkspace() | |
10900 | 545 | backend = circup.DiskBackend(devicePath, circup.logger) |
9740 | 546 | |
10690
fab36645aa7d
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10621
diff
changeset
|
547 | cpyVersion, _board_id = circup.get_circuitpython_version(devicePath) |
9740 | 548 | circup.CPY_VERSION = cpyVersion |
549 | ||
550 | with EricOverrideCursor(): | |
551 | availableModules = circup.get_bundle_versions(circup.get_bundles_list()) | |
552 | moduleNames = {} | |
553 | for module, metadata in availableModules.items(): | |
554 | moduleNames[module.replace(".py", "")] = metadata | |
555 | toBeInstalled = circup.get_dependencies(installs, mod_names=moduleNames) | |
10900 | 556 | deviceModules = backend.get_device_versions() |
9740 | 557 | if toBeInstalled is not None: |
558 | dependencies = [m for m in toBeInstalled if m not in installs] | |
559 | ok = EricMessageBox.yesNo( | |
560 | None, | |
561 | self.tr("Install Modules"), | |
562 | self.tr("""<p>Ready to install these modules?{0}{1}</p>""").format( | |
563 | """<ul><li>{0}</li></ul>""".format( | |
564 | "</li><li>".join(sorted(installs)) | |
565 | ), | |
10621
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
566 | ( |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
567 | self.tr("Dependencies:") |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
568 | + """<ul><li>{0}</li></ul>""".format( |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
569 | "</li><li>".join(sorted(dependencies)) |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
570 | ) |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
571 | if dependencies |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
572 | else "" |
f5631f40c4d9
Corrected some code formatting issues.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10439
diff
changeset
|
573 | ), |
9740 | 574 | ), |
575 | yesDefault=True, | |
576 | ) | |
577 | if ok: | |
578 | installedModules = [] | |
579 | with EricOverrideCursor(): | |
580 | for library in toBeInstalled: | |
581 | success = circup.install_module( | |
582 | devicePath, | |
583 | deviceModules, | |
584 | library, | |
585 | self.__installPyAct.isChecked(), | |
586 | moduleNames, | |
587 | ) | |
588 | if success: | |
589 | installedModules.append(library) | |
590 | ||
591 | if installedModules: | |
592 | EricMessageBox.information( | |
593 | None, | |
594 | self.tr("Install Modules"), | |
595 | self.tr( | |
596 | "<p>Installation complete. These modules were installed" | |
597 | " successfully.{0}</p>" | |
598 | ).format( | |
599 | """<ul><li>{0}</li></ul>""".format( | |
600 | "</li><li>".join(sorted(installedModules)) | |
601 | ), | |
602 | ), | |
603 | ) | |
604 | else: | |
605 | EricMessageBox.information( | |
606 | None, | |
607 | self.tr("Install Modules"), | |
608 | self.tr( | |
609 | "<p>Installation complete. No modules were installed.</p>" | |
610 | ), | |
611 | ) | |
612 | else: | |
613 | EricMessageBox.information( | |
614 | None, | |
615 | self.tr("Install Modules"), | |
616 | self.tr("<p>No modules installation is required.</p>"), | |
617 | ) | |
618 | ||
619 | @pyqtSlot() | |
620 | def __uninstallModules(self): | |
621 | """ | |
622 | Private slot to uninstall modules from the connected device. | |
623 | """ | |
624 | devicePath = self.__device.getWorkspace() | |
625 | libraryPath = os.path.join(devicePath, "lib") | |
626 | ||
627 | with EricOverrideCursor(): | |
10900 | 628 | backend = circup.DiskBackend(devicePath, circup.logger) |
629 | deviceModules = backend.get_device_versions() | |
9740 | 630 | modNames = {} |
631 | for moduleItem, metadata in deviceModules.items(): | |
632 | modNames[moduleItem.replace(".py", "").lower()] = metadata | |
633 | ||
634 | dlg = EricListSelectionDialog( | |
10373
093dcebe5ecb
Corrected some uses of dict.keys(), dict.values() and dict.items().
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10170
diff
changeset
|
635 | sorted(modNames), |
9740 | 636 | title=self.tr("Uninstall Modules"), |
637 | message=self.tr("Select the modules/packages to be uninstalled:"), | |
638 | checkBoxSelection=True, | |
11006
a671918232f3
Modified modal dialog usage to always include a valid parent (needed for Wayland).
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
11005
diff
changeset
|
639 | parent=self.__device.microPython, |
9740 | 640 | ) |
641 | if dlg.exec() == QDialog.DialogCode.Accepted: | |
642 | names = dlg.getSelection() | |
643 | for name in names: | |
644 | modulePath = modNames[name]["path"] | |
645 | if os.path.isdir(modulePath): | |
646 | target = os.path.basename(os.path.dirname(modulePath)) | |
647 | targetPath = os.path.join(libraryPath, target) | |
648 | # Remove the package directory. | |
649 | shutil.rmtree(targetPath) | |
650 | else: | |
651 | target = os.path.basename(modulePath) | |
652 | targetPath = os.path.join(libraryPath, target) | |
653 | # Remove the module file | |
654 | os.remove(targetPath) | |
655 | ||
656 | EricMessageBox.information( | |
657 | None, | |
658 | self.tr("Uninstall Modules"), | |
659 | self.tr( | |
660 | """<p>These modules/packages were uninstalled from the connected""" | |
661 | """ device.{0}</p>""" | |
662 | ).format("""<ul><li>{0}</li></ul>""".format("</li><li>".join(names))), | |
663 | ) | |
664 | ||
665 | @pyqtSlot() | |
666 | def __generateRequirements(self): | |
667 | """ | |
668 | Private slot to generate requirements for the connected device. | |
669 | """ | |
670 | from .RequirementsDialog import RequirementsDialog | |
671 | ||
672 | devicePath = self.__device.getWorkspace() | |
673 | ||
10690
fab36645aa7d
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10621
diff
changeset
|
674 | cpyVersion, _board_id = circup.get_circuitpython_version(devicePath) |
9740 | 675 | circup.CPY_VERSION = cpyVersion |
676 | ||
11005
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
677 | dlg = RequirementsDialog( |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
678 | devicePath=devicePath, parent=self.__device.microPython |
b918c6c2736b
MicroPython Interface
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
10900
diff
changeset
|
679 | ) |
9740 | 680 | dlg.exec() |
681 | ||
9869
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
682 | @pyqtSlot() |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
683 | def __showCachePath(self): |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
684 | """ |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
685 | Private slot to show the path used by 'circup' to store the downloaded bundles. |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
686 | """ |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
687 | EricMessageBox.information( |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
688 | None, |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
689 | self.tr("Show Local Cache Path"), |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
690 | self.tr( |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
691 | "<p><b>circup</b> stores the downloaded CircuitPython bundles in this" |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
692 | " directory.</p><p>{0}</p>" |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
693 | ).format(circup.DATA_DIR), |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
694 | ) |
fb2c71c1c7d7
CircuitPython updater
Detlev Offenbach <detlev@die-offenbachs.de>
parents:
9817
diff
changeset
|
695 | |
9740 | 696 | |
697 | def isCircupAvailable(): | |
698 | """ | |
699 | Function to check for the availability of 'circup'. | |
700 | ||
701 | @return flag indicating the availability of 'circup' | |
702 | @rtype bool | |
703 | """ | |
704 | global circup | |
705 | ||
706 | return circup is not None |