|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2007 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog showing the available plugins. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import zipfile |
|
12 import glob |
|
13 import re |
|
14 |
|
15 from PyQt6.QtCore import ( |
|
16 pyqtSignal, pyqtSlot, Qt, QFile, QIODevice, QUrl, QProcess, QPoint, |
|
17 QCoreApplication |
|
18 ) |
|
19 from PyQt6.QtWidgets import ( |
|
20 QWidget, QDialogButtonBox, QAbstractButton, QTreeWidgetItem, QDialog, |
|
21 QVBoxLayout, QHBoxLayout, QMenu, QLabel, QToolButton |
|
22 ) |
|
23 from PyQt6.QtNetwork import ( |
|
24 QNetworkAccessManager, QNetworkRequest, QNetworkReply, QNetworkInformation |
|
25 ) |
|
26 |
|
27 from .Ui_PluginRepositoryDialog import Ui_PluginRepositoryDialog |
|
28 |
|
29 from EricWidgets import EricMessageBox |
|
30 from EricWidgets.EricMainWindow import EricMainWindow |
|
31 from EricWidgets.EricApplication import ericApp |
|
32 |
|
33 from EricNetwork.EricNetworkProxyFactory import proxyAuthenticationRequired |
|
34 try: |
|
35 from EricNetwork.EricSslErrorHandler import ( |
|
36 EricSslErrorHandler, EricSslErrorState |
|
37 ) |
|
38 SSL_AVAILABLE = True |
|
39 except ImportError: |
|
40 SSL_AVAILABLE = False |
|
41 |
|
42 import Globals |
|
43 import Utilities |
|
44 import Preferences |
|
45 |
|
46 import UI.PixmapCache |
|
47 |
|
48 from eric7config import getConfig |
|
49 |
|
50 |
|
51 class PluginRepositoryWidget(QWidget, Ui_PluginRepositoryDialog): |
|
52 """ |
|
53 Class implementing a dialog showing the available plugins. |
|
54 |
|
55 @signal closeAndInstall() emitted when the Close & Install button is |
|
56 pressed |
|
57 """ |
|
58 closeAndInstall = pyqtSignal() |
|
59 |
|
60 DescrRole = Qt.ItemDataRole.UserRole |
|
61 UrlRole = Qt.ItemDataRole.UserRole + 1 |
|
62 FilenameRole = Qt.ItemDataRole.UserRole + 2 |
|
63 AuthorRole = Qt.ItemDataRole.UserRole + 3 |
|
64 |
|
65 PluginStatusUpToDate = 0 |
|
66 PluginStatusNew = 1 |
|
67 PluginStatusLocalUpdate = 2 |
|
68 PluginStatusRemoteUpdate = 3 |
|
69 PluginStatusError = 4 |
|
70 |
|
71 def __init__(self, pluginManager, integrated=False, parent=None): |
|
72 """ |
|
73 Constructor |
|
74 |
|
75 @param pluginManager reference to the plugin manager object |
|
76 @type PluginManager |
|
77 @param integrated flag indicating the integration into the sidebar |
|
78 @type bool |
|
79 @param parent parent of this dialog |
|
80 @type QWidget |
|
81 """ |
|
82 super().__init__(parent) |
|
83 self.setupUi(self) |
|
84 |
|
85 if pluginManager is None: |
|
86 # started as external plug-in repository dialog |
|
87 from .PluginManager import PluginManager |
|
88 self.__pluginManager = PluginManager() |
|
89 self.__external = True |
|
90 else: |
|
91 self.__pluginManager = pluginManager |
|
92 self.__external = False |
|
93 self.__integratedWidget = integrated |
|
94 |
|
95 if integrated: |
|
96 self.layout().setContentsMargins(0, 3, 0, 0) |
|
97 |
|
98 if self.__integratedWidget: |
|
99 self.__actionButtonsLayout = QHBoxLayout() |
|
100 self.__actionButtonsLayout.addStretch() |
|
101 |
|
102 self.__updateButton = QToolButton(self) |
|
103 self.__updateButton.setIcon(UI.PixmapCache.getIcon("reload")) |
|
104 self.__updateButton.setToolTip(self.tr("Update")) |
|
105 self.__updateButton.clicked.connect(self.__updateList) |
|
106 self.__actionButtonsLayout.addWidget(self.__updateButton) |
|
107 |
|
108 self.__downloadButton = QToolButton(self) |
|
109 self.__downloadButton.setIcon(UI.PixmapCache.getIcon("download")) |
|
110 self.__downloadButton.setToolTip(self.tr("Download")) |
|
111 self.__downloadButton.clicked.connect(self.__downloadButtonClicked) |
|
112 self.__actionButtonsLayout.addWidget(self.__downloadButton) |
|
113 |
|
114 self.__downloadInstallButton = QToolButton(self) |
|
115 self.__downloadInstallButton.setIcon( |
|
116 UI.PixmapCache.getIcon("downloadPlus")) |
|
117 self.__downloadInstallButton.setToolTip( |
|
118 self.tr("Download & Install")) |
|
119 self.__downloadInstallButton.clicked.connect( |
|
120 self.__downloadInstallButtonClicked) |
|
121 self.__actionButtonsLayout.addWidget(self.__downloadInstallButton) |
|
122 |
|
123 self.__downloadCancelButton = QToolButton(self) |
|
124 self.__downloadCancelButton.setIcon( |
|
125 UI.PixmapCache.getIcon("cancel")) |
|
126 self.__downloadCancelButton.setToolTip(self.tr("Cancel")) |
|
127 self.__downloadCancelButton.clicked.connect(self.__downloadCancel) |
|
128 self.__actionButtonsLayout.addWidget(self.__downloadCancelButton) |
|
129 |
|
130 self.__installButton = QToolButton(self) |
|
131 self.__installButton.setIcon(UI.PixmapCache.getIcon("plus")) |
|
132 self.__installButton.setToolTip(self.tr("Install")) |
|
133 self.__installButton.clicked.connect(self.__closeAndInstall) |
|
134 self.__actionButtonsLayout.addWidget(self.__installButton) |
|
135 |
|
136 self.__actionButtonsLayout.addStretch() |
|
137 |
|
138 self.layout().addLayout(self.__actionButtonsLayout) |
|
139 self.buttonBox.hide() |
|
140 else: |
|
141 self.__updateButton = self.buttonBox.addButton( |
|
142 self.tr("Update"), QDialogButtonBox.ButtonRole.ActionRole) |
|
143 self.__downloadButton = self.buttonBox.addButton( |
|
144 self.tr("Download"), QDialogButtonBox.ButtonRole.ActionRole) |
|
145 self.__downloadInstallButton = self.buttonBox.addButton( |
|
146 self.tr("Download && Install"), |
|
147 QDialogButtonBox.ButtonRole.ActionRole) |
|
148 self.__downloadCancelButton = self.buttonBox.addButton( |
|
149 self.tr("Cancel"), QDialogButtonBox.ButtonRole.ActionRole) |
|
150 self.__installButton = self.buttonBox.addButton( |
|
151 self.tr("Close && Install"), |
|
152 QDialogButtonBox.ButtonRole.ActionRole) |
|
153 if not self.__integratedWidget: |
|
154 self.__closeButton = self.buttonBox.addButton( |
|
155 self.tr("Close"), QDialogButtonBox.ButtonRole.RejectRole) |
|
156 self.__closeButton.setEnabled(True) |
|
157 |
|
158 self.__downloadButton.setEnabled(False) |
|
159 self.__downloadInstallButton.setEnabled(False) |
|
160 self.__downloadCancelButton.setEnabled(False) |
|
161 self.__installButton.setEnabled(False) |
|
162 |
|
163 self.repositoryUrlEdit.setText( |
|
164 Preferences.getUI("PluginRepositoryUrl7")) |
|
165 |
|
166 if self.__integratedWidget: |
|
167 self.repositoryList.setHeaderHidden(True) |
|
168 else: |
|
169 self.repositoryList.headerItem().setText( |
|
170 self.repositoryList.columnCount(), "") |
|
171 self.repositoryList.header().setSortIndicator( |
|
172 0, Qt.SortOrder.AscendingOrder) |
|
173 |
|
174 self.__pluginContextMenu = QMenu(self) |
|
175 self.__hideAct = self.__pluginContextMenu.addAction( |
|
176 self.tr("Hide"), self.__hidePlugin) |
|
177 self.__hideSelectedAct = self.__pluginContextMenu.addAction( |
|
178 self.tr("Hide Selected"), self.__hideSelectedPlugins) |
|
179 self.__pluginContextMenu.addSeparator() |
|
180 self.__showAllAct = self.__pluginContextMenu.addAction( |
|
181 self.tr("Show All"), self.__showAllPlugins) |
|
182 self.__pluginContextMenu.addSeparator() |
|
183 self.__pluginContextMenu.addAction( |
|
184 self.tr("Cleanup Downloads"), self.__cleanupDownloads) |
|
185 |
|
186 self.pluginRepositoryFile = os.path.join(Utilities.getConfigDir(), |
|
187 "PluginRepository") |
|
188 |
|
189 self.__pluginManager.pluginRepositoryFileDownloaded.connect( |
|
190 self.__populateList) |
|
191 |
|
192 # attributes for the network objects |
|
193 self.__networkManager = QNetworkAccessManager(self) |
|
194 self.__networkManager.proxyAuthenticationRequired.connect( |
|
195 proxyAuthenticationRequired) |
|
196 if SSL_AVAILABLE: |
|
197 self.__sslErrorHandler = EricSslErrorHandler(self) |
|
198 self.__networkManager.sslErrors.connect(self.__sslErrors) |
|
199 self.__replies = [] |
|
200 |
|
201 if ( |
|
202 Preferences.getUI("DynamicOnlineCheck") and |
|
203 QNetworkInformation.load(QNetworkInformation.Feature.Reachability) |
|
204 ): |
|
205 self.__reachabilityChanged( |
|
206 QNetworkInformation.instance().reachability()) |
|
207 QNetworkInformation.instance().reachabilityChanged.connect( |
|
208 self.__reachabilityChanged) |
|
209 else: |
|
210 # assume to be 'always online' if no backend could be loaded or |
|
211 # dynamic online check is switched of |
|
212 self.__reachabilityChanged(QNetworkInformation.Reachability.Online) |
|
213 |
|
214 self.__pluginsToDownload = [] |
|
215 self.__pluginsDownloaded = [] |
|
216 self.__isDownloadInstall = False |
|
217 self.__allDownloadedOk = False |
|
218 |
|
219 self.__hiddenPlugins = Preferences.getPluginManager("HiddenPlugins") |
|
220 |
|
221 self.__populateList() |
|
222 |
|
223 def __reachabilityChanged(self, reachability): |
|
224 """ |
|
225 Private slot handling reachability state changes. |
|
226 |
|
227 @param reachability new reachability state |
|
228 @type QNetworkInformation.Reachability |
|
229 """ |
|
230 online = reachability == QNetworkInformation.Reachability.Online |
|
231 self.__online = online |
|
232 |
|
233 self.__updateButton.setEnabled(online) |
|
234 self.on_repositoryList_itemSelectionChanged() |
|
235 |
|
236 if not self.__integratedWidget: |
|
237 msg = ( |
|
238 self.tr("Internet Reachability Status: Reachable") |
|
239 if online else |
|
240 self.tr("Internet Reachability Status: Not Reachable") |
|
241 ) |
|
242 self.statusLabel.setText(msg) |
|
243 |
|
244 @pyqtSlot(QAbstractButton) |
|
245 def on_buttonBox_clicked(self, button): |
|
246 """ |
|
247 Private slot to handle the click of a button of the button box. |
|
248 |
|
249 @param button reference to the button pressed (QAbstractButton) |
|
250 """ |
|
251 if button == self.__updateButton: |
|
252 self.__updateList() |
|
253 elif button == self.__downloadButton: |
|
254 self.__downloadButtonClicked() |
|
255 elif button == self.__downloadInstallButton: |
|
256 self.__downloadInstallButtonClicked() |
|
257 elif button == self.__downloadCancelButton: |
|
258 self.__downloadCancel() |
|
259 elif button == self.__installButton: |
|
260 self.__closeAndInstall() |
|
261 |
|
262 @pyqtSlot() |
|
263 def __downloadButtonClicked(self): |
|
264 """ |
|
265 Private slot to handle a click of the Download button. |
|
266 """ |
|
267 self.__isDownloadInstall = False |
|
268 self.__downloadPlugins() |
|
269 |
|
270 @pyqtSlot() |
|
271 def __downloadInstallButtonClicked(self): |
|
272 """ |
|
273 Private slot to handle a click of the Download & Install button. |
|
274 """ |
|
275 self.__isDownloadInstall = True |
|
276 self.__allDownloadedOk = True |
|
277 self.__downloadPlugins() |
|
278 |
|
279 def __formatDescription(self, lines): |
|
280 """ |
|
281 Private method to format the description. |
|
282 |
|
283 @param lines lines of the description (list of strings) |
|
284 @return formatted description (string) |
|
285 """ |
|
286 # remove empty line at start and end |
|
287 newlines = lines[:] |
|
288 if len(newlines) and newlines[0] == '': |
|
289 del newlines[0] |
|
290 if len(newlines) and newlines[-1] == '': |
|
291 del newlines[-1] |
|
292 |
|
293 # replace empty lines by newline character |
|
294 index = 0 |
|
295 while index < len(newlines): |
|
296 if newlines[index] == '': |
|
297 newlines[index] = '\n' |
|
298 index += 1 |
|
299 |
|
300 # join lines by a blank |
|
301 return ' '.join(newlines) |
|
302 |
|
303 def __changeScheme(self, url, newScheme=""): |
|
304 """ |
|
305 Private method to change the scheme of the given URL. |
|
306 |
|
307 @param url URL to be modified |
|
308 @type str |
|
309 @param newScheme scheme to be set for the given URL |
|
310 @return modified URL |
|
311 @rtype str |
|
312 """ |
|
313 if not newScheme: |
|
314 newScheme = self.repositoryUrlEdit.text().split("//", 1)[0] |
|
315 |
|
316 return newScheme + "//" + url.split("//", 1)[1] |
|
317 |
|
318 @pyqtSlot(QPoint) |
|
319 def on_repositoryList_customContextMenuRequested(self, pos): |
|
320 """ |
|
321 Private slot to show the context menu. |
|
322 |
|
323 @param pos position to show the menu (QPoint) |
|
324 """ |
|
325 self.__hideAct.setEnabled( |
|
326 self.repositoryList.currentItem() is not None and |
|
327 len(self.__selectedItems()) == 1) |
|
328 self.__hideSelectedAct.setEnabled( |
|
329 len(self.__selectedItems()) > 1) |
|
330 self.__showAllAct.setEnabled(bool(self.__hasHiddenPlugins())) |
|
331 self.__pluginContextMenu.popup(self.repositoryList.mapToGlobal(pos)) |
|
332 |
|
333 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) |
|
334 def on_repositoryList_currentItemChanged(self, current, previous): |
|
335 """ |
|
336 Private slot to handle the change of the current item. |
|
337 |
|
338 @param current reference to the new current item (QTreeWidgetItem) |
|
339 @param previous reference to the old current item (QTreeWidgetItem) |
|
340 """ |
|
341 if self.__repositoryMissing or current is None: |
|
342 self.descriptionEdit.clear() |
|
343 self.authorEdit.clear() |
|
344 return |
|
345 |
|
346 url = current.data(0, PluginRepositoryWidget.UrlRole) |
|
347 url = "" if url is None else self.__changeScheme(url) |
|
348 self.urlEdit.setText(url) |
|
349 self.descriptionEdit.setPlainText( |
|
350 current.data(0, PluginRepositoryWidget.DescrRole) and |
|
351 self.__formatDescription( |
|
352 current.data(0, PluginRepositoryWidget.DescrRole)) or "") |
|
353 self.authorEdit.setText( |
|
354 current.data(0, PluginRepositoryWidget.AuthorRole) or "") |
|
355 |
|
356 def __selectedItems(self): |
|
357 """ |
|
358 Private method to get all selected items without the toplevel ones. |
|
359 |
|
360 @return list of selected items (list) |
|
361 """ |
|
362 ql = self.repositoryList.selectedItems() |
|
363 for index in range(self.repositoryList.topLevelItemCount()): |
|
364 ti = self.repositoryList.topLevelItem(index) |
|
365 if ti in ql: |
|
366 ql.remove(ti) |
|
367 return ql |
|
368 |
|
369 @pyqtSlot() |
|
370 def on_repositoryList_itemSelectionChanged(self): |
|
371 """ |
|
372 Private slot to handle a change of the selection. |
|
373 """ |
|
374 enable = bool(self.__selectedItems()) |
|
375 self.__downloadButton.setEnabled(enable and self.__online) |
|
376 self.__downloadInstallButton.setEnabled(enable and self.__online) |
|
377 self.__installButton.setEnabled(enable) |
|
378 |
|
379 def reloadList(self): |
|
380 """ |
|
381 Public method to reload the list of plugins. |
|
382 """ |
|
383 self.__populateList() |
|
384 |
|
385 @pyqtSlot() |
|
386 def __updateList(self): |
|
387 """ |
|
388 Private slot to download a new list and display the contents. |
|
389 """ |
|
390 url = self.repositoryUrlEdit.text() |
|
391 self.__pluginManager.downLoadRepositoryFile(url=url) |
|
392 |
|
393 def __downloadRepositoryFileDone(self, status, filename): |
|
394 """ |
|
395 Private method called after the repository file was downloaded. |
|
396 |
|
397 @param status flaging indicating a successful download (boolean) |
|
398 @param filename full path of the downloaded file (string) |
|
399 """ |
|
400 self.__populateList() |
|
401 |
|
402 def __downloadPluginDone(self, status, filename): |
|
403 """ |
|
404 Private method called, when the download of a plugin is finished. |
|
405 |
|
406 @param status flag indicating a successful download (boolean) |
|
407 @param filename full path of the downloaded file (string) |
|
408 """ |
|
409 if status: |
|
410 self.__pluginsDownloaded.append(filename) |
|
411 if self.__isDownloadInstall: |
|
412 self.__allDownloadedOk &= status |
|
413 |
|
414 if len(self.__pluginsToDownload): |
|
415 self.__pluginsToDownload.pop(0) |
|
416 |
|
417 if len(self.__pluginsToDownload): |
|
418 self.__downloadPlugin() |
|
419 else: |
|
420 self.__downloadPluginsDone() |
|
421 |
|
422 def __downloadPlugin(self): |
|
423 """ |
|
424 Private method to download the next plugin. |
|
425 """ |
|
426 self.__downloadFile(self.__pluginsToDownload[0][0], |
|
427 self.__pluginsToDownload[0][1], |
|
428 self.__downloadPluginDone) |
|
429 |
|
430 def __downloadPlugins(self): |
|
431 """ |
|
432 Private slot to download the selected plugins. |
|
433 """ |
|
434 self.__pluginsDownloaded = [] |
|
435 self.__pluginsToDownload = [] |
|
436 self.__downloadButton.setEnabled(False) |
|
437 self.__downloadInstallButton.setEnabled(False) |
|
438 self.__installButton.setEnabled(False) |
|
439 |
|
440 newScheme = self.repositoryUrlEdit.text().split("//", 1)[0] |
|
441 for itm in self.repositoryList.selectedItems(): |
|
442 if itm not in [self.__stableItem, self.__unstableItem, |
|
443 self.__unknownItem, self.__obsoleteItem]: |
|
444 url = self.__changeScheme( |
|
445 itm.data(0, PluginRepositoryWidget.UrlRole), |
|
446 newScheme) |
|
447 filename = os.path.join( |
|
448 Preferences.getPluginManager("DownloadPath"), |
|
449 itm.data(0, PluginRepositoryWidget.FilenameRole)) |
|
450 self.__pluginsToDownload.append((url, filename)) |
|
451 if self.__pluginsToDownload: |
|
452 self.__downloadPlugin() |
|
453 |
|
454 def __downloadPluginsDone(self): |
|
455 """ |
|
456 Private method called, when the download of the plugins is finished. |
|
457 """ |
|
458 self.__downloadButton.setEnabled(len(self.__selectedItems())) |
|
459 self.__downloadInstallButton.setEnabled(len(self.__selectedItems())) |
|
460 self.__installButton.setEnabled(len(self.__selectedItems())) |
|
461 ui = (ericApp().getObject("UserInterface") |
|
462 if not self.__external else None) |
|
463 if ui is not None: |
|
464 ui.showNotification( |
|
465 UI.PixmapCache.getPixmap("plugin48"), |
|
466 self.tr("Download Plugin Files"), |
|
467 self.tr("""The requested plugins were downloaded.""")) |
|
468 |
|
469 if self.__isDownloadInstall: |
|
470 self.closeAndInstall.emit() |
|
471 else: |
|
472 if ui is None: |
|
473 EricMessageBox.information( |
|
474 self, |
|
475 self.tr("Download Plugin Files"), |
|
476 self.tr("""The requested plugins were downloaded.""")) |
|
477 |
|
478 self.downloadProgress.setValue(0) |
|
479 |
|
480 # repopulate the list to update the refresh icons |
|
481 self.__populateList() |
|
482 |
|
483 def __resortRepositoryList(self): |
|
484 """ |
|
485 Private method to resort the tree. |
|
486 """ |
|
487 self.repositoryList.sortItems( |
|
488 self.repositoryList.sortColumn(), |
|
489 self.repositoryList.header().sortIndicatorOrder()) |
|
490 |
|
491 def __populateList(self): |
|
492 """ |
|
493 Private method to populate the list of available plugins. |
|
494 """ |
|
495 self.repositoryList.clear() |
|
496 self.__stableItem = None |
|
497 self.__unstableItem = None |
|
498 self.__unknownItem = None |
|
499 self.__obsoleteItem = None |
|
500 |
|
501 self.__newItems = 0 |
|
502 self.__updateLocalItems = 0 |
|
503 self.__updateRemoteItems = 0 |
|
504 |
|
505 self.downloadProgress.setValue(0) |
|
506 |
|
507 if os.path.exists(self.pluginRepositoryFile): |
|
508 self.__repositoryMissing = False |
|
509 f = QFile(self.pluginRepositoryFile) |
|
510 if f.open(QIODevice.OpenModeFlag.ReadOnly): |
|
511 from EricXML.PluginRepositoryReader import ( |
|
512 PluginRepositoryReader |
|
513 ) |
|
514 reader = PluginRepositoryReader(f, self.addEntry) |
|
515 reader.readXML() |
|
516 self.repositoryList.resizeColumnToContents(0) |
|
517 self.repositoryList.resizeColumnToContents(1) |
|
518 self.repositoryList.resizeColumnToContents(2) |
|
519 self.__resortRepositoryList() |
|
520 url = Preferences.getUI("PluginRepositoryUrl7") |
|
521 if url != self.repositoryUrlEdit.text(): |
|
522 self.repositoryUrlEdit.setText(url) |
|
523 EricMessageBox.warning( |
|
524 self, |
|
525 self.tr("Plugins Repository URL Changed"), |
|
526 self.tr( |
|
527 """The URL of the Plugins Repository has""" |
|
528 """ changed. Select the "Update" button to get""" |
|
529 """ the new repository file.""")) |
|
530 else: |
|
531 EricMessageBox.critical( |
|
532 self, |
|
533 self.tr("Read plugins repository file"), |
|
534 self.tr("<p>The plugins repository file <b>{0}</b> " |
|
535 "could not be read. Select Update</p>") |
|
536 .format(self.pluginRepositoryFile)) |
|
537 else: |
|
538 self.__repositoryMissing = True |
|
539 QTreeWidgetItem( |
|
540 self.repositoryList, |
|
541 ["", self.tr( |
|
542 "No plugin repository file available.\nSelect Update.") |
|
543 ]) |
|
544 self.repositoryList.resizeColumnToContents(1) |
|
545 |
|
546 self.newLabel.setText(self.tr("New: <b>{0}</b>") |
|
547 .format(self.__newItems)) |
|
548 self.updateLocalLabel.setText(self.tr("Local Updates: <b>{0}</b>") |
|
549 .format(self.__updateLocalItems)) |
|
550 self.updateRemoteLabel.setText(self.tr("Remote Updates: <b>{0}</b>") |
|
551 .format(self.__updateRemoteItems)) |
|
552 |
|
553 def __downloadFile(self, url, filename, doneMethod=None): |
|
554 """ |
|
555 Private slot to download the given file. |
|
556 |
|
557 @param url URL for the download (string) |
|
558 @param filename local name of the file (string) |
|
559 @param doneMethod method to be called when done |
|
560 """ |
|
561 if self.__online: |
|
562 self.__updateButton.setEnabled(False) |
|
563 self.__downloadButton.setEnabled(False) |
|
564 self.__downloadInstallButton.setEnabled(False) |
|
565 if not self.__integratedWidget: |
|
566 self.__closeButton.setEnabled(False) |
|
567 self.__downloadCancelButton.setEnabled(True) |
|
568 |
|
569 self.statusLabel.setText(url) |
|
570 |
|
571 request = QNetworkRequest(QUrl(url)) |
|
572 request.setAttribute( |
|
573 QNetworkRequest.Attribute.CacheLoadControlAttribute, |
|
574 QNetworkRequest.CacheLoadControl.AlwaysNetwork) |
|
575 reply = self.__networkManager.get(request) |
|
576 reply.finished.connect( |
|
577 lambda: self.__downloadFileDone(reply, filename, doneMethod)) |
|
578 reply.downloadProgress.connect(self.__downloadProgress) |
|
579 self.__replies.append(reply) |
|
580 else: |
|
581 EricMessageBox.warning( |
|
582 self, |
|
583 self.tr("Error downloading file"), |
|
584 self.tr( |
|
585 """<p>Could not download the requested file""" |
|
586 """ from {0}.</p><p>Error: {1}</p>""" |
|
587 ).format(url, self.tr("No connection to Internet."))) |
|
588 |
|
589 def __downloadFileDone(self, reply, fileName, doneMethod): |
|
590 """ |
|
591 Private method called, after the file has been downloaded |
|
592 from the Internet. |
|
593 |
|
594 @param reply reference to the reply object of the download |
|
595 @type QNetworkReply |
|
596 @param fileName local name of the file |
|
597 @type str |
|
598 @param doneMethod method to be called when done |
|
599 @type func |
|
600 """ |
|
601 self.__updateButton.setEnabled(True) |
|
602 if not self.__integratedWidget: |
|
603 self.__closeButton.setEnabled(True) |
|
604 self.__downloadCancelButton.setEnabled(False) |
|
605 |
|
606 ok = True |
|
607 if reply in self.__replies: |
|
608 self.__replies.remove(reply) |
|
609 if reply.error() != QNetworkReply.NetworkError.NoError: |
|
610 ok = False |
|
611 if ( |
|
612 reply.error() != |
|
613 QNetworkReply.NetworkError.OperationCanceledError |
|
614 ): |
|
615 EricMessageBox.warning( |
|
616 self, |
|
617 self.tr("Error downloading file"), |
|
618 self.tr( |
|
619 """<p>Could not download the requested file""" |
|
620 """ from {0}.</p><p>Error: {1}</p>""" |
|
621 ).format(reply.url().toString(), reply.errorString()) |
|
622 ) |
|
623 self.downloadProgress.setValue(0) |
|
624 if self.repositoryList.topLevelItemCount(): |
|
625 if self.repositoryList.currentItem() is None: |
|
626 self.repositoryList.setCurrentItem( |
|
627 self.repositoryList.topLevelItem(0)) |
|
628 else: |
|
629 self.__downloadButton.setEnabled( |
|
630 len(self.__selectedItems())) |
|
631 self.__downloadInstallButton.setEnabled( |
|
632 len(self.__selectedItems())) |
|
633 reply.deleteLater() |
|
634 return |
|
635 |
|
636 downloadIODevice = QFile(fileName + ".tmp") |
|
637 downloadIODevice.open(QIODevice.OpenModeFlag.WriteOnly) |
|
638 # read data in chunks |
|
639 chunkSize = 64 * 1024 * 1024 |
|
640 while True: |
|
641 data = reply.read(chunkSize) |
|
642 if data is None or len(data) == 0: |
|
643 break |
|
644 downloadIODevice.write(data) |
|
645 downloadIODevice.close() |
|
646 if QFile.exists(fileName): |
|
647 QFile.remove(fileName) |
|
648 downloadIODevice.rename(fileName) |
|
649 reply.deleteLater() |
|
650 |
|
651 if doneMethod is not None: |
|
652 doneMethod(ok, fileName) |
|
653 |
|
654 def __downloadCancel(self, reply=None): |
|
655 """ |
|
656 Private slot to cancel the current download. |
|
657 |
|
658 @param reply reference to the network reply |
|
659 @type QNetworkReply |
|
660 """ |
|
661 if reply is None and bool(self.__replies): |
|
662 reply = self.__replies[0] |
|
663 self.__pluginsToDownload = [] |
|
664 if reply is not None: |
|
665 reply.abort() |
|
666 |
|
667 def __downloadProgress(self, done, total): |
|
668 """ |
|
669 Private slot to show the download progress. |
|
670 |
|
671 @param done number of bytes downloaded so far (integer) |
|
672 @param total total bytes to be downloaded (integer) |
|
673 """ |
|
674 if total: |
|
675 self.downloadProgress.setMaximum(total) |
|
676 self.downloadProgress.setValue(done) |
|
677 |
|
678 def addEntry(self, name, short, description, url, author, version, |
|
679 filename, status): |
|
680 """ |
|
681 Public method to add an entry to the list. |
|
682 |
|
683 @param name data for the name field (string) |
|
684 @param short data for the short field (string) |
|
685 @param description data for the description field (list of strings) |
|
686 @param url data for the url field (string) |
|
687 @param author data for the author field (string) |
|
688 @param version data for the version field (string) |
|
689 @param filename data for the filename field (string) |
|
690 @param status status of the plugin (string [stable, unstable, unknown]) |
|
691 """ |
|
692 pluginName = filename.rsplit("-", 1)[0] |
|
693 if pluginName in self.__hiddenPlugins: |
|
694 return |
|
695 |
|
696 if status == "stable": |
|
697 if self.__stableItem is None: |
|
698 self.__stableItem = QTreeWidgetItem( |
|
699 self.repositoryList, [self.tr("Stable")]) |
|
700 self.__stableItem.setExpanded(True) |
|
701 parent = self.__stableItem |
|
702 elif status == "unstable": |
|
703 if self.__unstableItem is None: |
|
704 self.__unstableItem = QTreeWidgetItem( |
|
705 self.repositoryList, [self.tr("Unstable")]) |
|
706 self.__unstableItem.setExpanded(True) |
|
707 parent = self.__unstableItem |
|
708 elif status == "obsolete": |
|
709 if self.__obsoleteItem is None: |
|
710 self.__obsoleteItem = QTreeWidgetItem( |
|
711 self.repositoryList, [self.tr("Obsolete")]) |
|
712 self.__obsoleteItem.setExpanded(True) |
|
713 parent = self.__obsoleteItem |
|
714 else: |
|
715 if self.__unknownItem is None: |
|
716 self.__unknownItem = QTreeWidgetItem( |
|
717 self.repositoryList, [self.tr("Unknown")]) |
|
718 self.__unknownItem.setExpanded(True) |
|
719 parent = self.__unknownItem |
|
720 |
|
721 if self.__integratedWidget: |
|
722 entryFormat = "<b>{0}</b> - Version: <i>{1}</i><br/>{2}" |
|
723 itm = QTreeWidgetItem(parent) |
|
724 itm.setFirstColumnSpanned(True) |
|
725 label = QLabel(entryFormat.format(name, version, short)) |
|
726 self.repositoryList.setItemWidget(itm, 0, label) |
|
727 else: |
|
728 itm = QTreeWidgetItem(parent, [name, version, short]) |
|
729 |
|
730 itm.setData(0, PluginRepositoryWidget.UrlRole, url) |
|
731 itm.setData(0, PluginRepositoryWidget.FilenameRole, filename) |
|
732 itm.setData(0, PluginRepositoryWidget.AuthorRole, author) |
|
733 itm.setData(0, PluginRepositoryWidget.DescrRole, description) |
|
734 |
|
735 iconColumn = 0 if self.__integratedWidget else 1 |
|
736 updateStatus = self.__updateStatus(filename, version) |
|
737 if updateStatus == PluginRepositoryWidget.PluginStatusUpToDate: |
|
738 itm.setIcon(iconColumn, UI.PixmapCache.getIcon("empty")) |
|
739 itm.setToolTip(iconColumn, self.tr("up-to-date")) |
|
740 elif updateStatus == PluginRepositoryWidget.PluginStatusNew: |
|
741 itm.setIcon(iconColumn, UI.PixmapCache.getIcon("download")) |
|
742 itm.setToolTip(iconColumn, self.tr("new download available")) |
|
743 self.__newItems += 1 |
|
744 elif updateStatus == PluginRepositoryWidget.PluginStatusLocalUpdate: |
|
745 itm.setIcon(iconColumn, UI.PixmapCache.getIcon("updateLocal")) |
|
746 itm.setToolTip(iconColumn, self.tr("update installable")) |
|
747 self.__updateLocalItems += 1 |
|
748 elif updateStatus == PluginRepositoryWidget.PluginStatusRemoteUpdate: |
|
749 itm.setIcon(iconColumn, UI.PixmapCache.getIcon("updateRemote")) |
|
750 itm.setToolTip(iconColumn, self.tr("updated download available")) |
|
751 self.__updateRemoteItems += 1 |
|
752 elif updateStatus == PluginRepositoryWidget.PluginStatusError: |
|
753 itm.setIcon(iconColumn, UI.PixmapCache.getIcon("warning")) |
|
754 itm.setToolTip(iconColumn, self.tr("error determining status")) |
|
755 |
|
756 def __updateStatus(self, filename, version): |
|
757 """ |
|
758 Private method to check the given archive update status. |
|
759 |
|
760 @param filename data for the filename field (string) |
|
761 @param version data for the version field (string) |
|
762 @return plug-in update status (integer, one of PluginStatusNew, |
|
763 PluginStatusUpToDate, PluginStatusLocalUpdate, |
|
764 PluginStatusRemoteUpdate) |
|
765 """ |
|
766 archive = os.path.join(Preferences.getPluginManager("DownloadPath"), |
|
767 filename) |
|
768 |
|
769 # check, if it is an update (i.e. we already have archives |
|
770 # with the same pattern) |
|
771 archivesPattern = archive.rsplit('-', 1)[0] + "-*.zip" |
|
772 if len(glob.glob(archivesPattern)) == 0: |
|
773 # Check against installed/loaded plug-ins |
|
774 pluginName = filename.rsplit('-', 1)[0] |
|
775 pluginDetails = self.__pluginManager.getPluginDetails(pluginName) |
|
776 if ( |
|
777 pluginDetails is None or |
|
778 pluginDetails["moduleName"] != pluginName |
|
779 ): |
|
780 return PluginRepositoryWidget.PluginStatusNew |
|
781 if pluginDetails["error"]: |
|
782 return PluginRepositoryWidget.PluginStatusError |
|
783 pluginVersionTuple = Globals.versionToTuple( |
|
784 pluginDetails["version"])[:3] |
|
785 versionTuple = Globals.versionToTuple(version)[:3] |
|
786 if pluginVersionTuple < versionTuple: |
|
787 return PluginRepositoryWidget.PluginStatusRemoteUpdate |
|
788 else: |
|
789 return PluginRepositoryWidget.PluginStatusUpToDate |
|
790 |
|
791 # check, if the archive exists |
|
792 if not os.path.exists(archive): |
|
793 return PluginRepositoryWidget.PluginStatusRemoteUpdate |
|
794 |
|
795 # check, if the archive is a valid zip file |
|
796 if not zipfile.is_zipfile(archive): |
|
797 return PluginRepositoryWidget.PluginStatusRemoteUpdate |
|
798 |
|
799 zipFile = zipfile.ZipFile(archive, "r") |
|
800 try: |
|
801 aversion = zipFile.read("VERSION").decode("utf-8") |
|
802 except KeyError: |
|
803 aversion = "" |
|
804 zipFile.close() |
|
805 |
|
806 if aversion == version: |
|
807 # Check against installed/loaded plug-ins |
|
808 pluginName = filename.rsplit('-', 1)[0] |
|
809 pluginDetails = self.__pluginManager.getPluginDetails(pluginName) |
|
810 if pluginDetails is None: |
|
811 return PluginRepositoryWidget.PluginStatusLocalUpdate |
|
812 if ( |
|
813 Globals.versionToTuple(pluginDetails["version"])[:3] < |
|
814 Globals.versionToTuple(version)[:3] |
|
815 ): |
|
816 return PluginRepositoryWidget.PluginStatusLocalUpdate |
|
817 else: |
|
818 return PluginRepositoryWidget.PluginStatusUpToDate |
|
819 else: |
|
820 return PluginRepositoryWidget.PluginStatusRemoteUpdate |
|
821 |
|
822 def __sslErrors(self, reply, errors): |
|
823 """ |
|
824 Private slot to handle SSL errors. |
|
825 |
|
826 @param reply reference to the reply object (QNetworkReply) |
|
827 @param errors list of SSL errors (list of QSslError) |
|
828 """ |
|
829 ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0] |
|
830 if ignored == EricSslErrorState.NOT_IGNORED: |
|
831 self.__downloadCancel(reply) |
|
832 |
|
833 def getDownloadedPlugins(self): |
|
834 """ |
|
835 Public method to get the list of recently downloaded plugin files. |
|
836 |
|
837 @return list of plugin filenames (list of strings) |
|
838 """ |
|
839 return self.__pluginsDownloaded |
|
840 |
|
841 @pyqtSlot(bool) |
|
842 def on_repositoryUrlEditButton_toggled(self, checked): |
|
843 """ |
|
844 Private slot to set the read only status of the repository URL line |
|
845 edit. |
|
846 |
|
847 @param checked state of the push button (boolean) |
|
848 """ |
|
849 self.repositoryUrlEdit.setReadOnly(not checked) |
|
850 |
|
851 def __closeAndInstall(self): |
|
852 """ |
|
853 Private method to close the dialog and invoke the install dialog. |
|
854 """ |
|
855 if not self.__pluginsDownloaded and self.__selectedItems(): |
|
856 for itm in self.__selectedItems(): |
|
857 filename = os.path.join( |
|
858 Preferences.getPluginManager("DownloadPath"), |
|
859 itm.data(0, PluginRepositoryWidget.FilenameRole)) |
|
860 self.__pluginsDownloaded.append(filename) |
|
861 self.closeAndInstall.emit() |
|
862 |
|
863 def __hidePlugin(self): |
|
864 """ |
|
865 Private slot to hide the current plug-in. |
|
866 """ |
|
867 itm = self.__selectedItems()[0] |
|
868 pluginName = (itm.data(0, PluginRepositoryWidget.FilenameRole) |
|
869 .rsplit("-", 1)[0]) |
|
870 self.__updateHiddenPluginsList([pluginName]) |
|
871 |
|
872 def __hideSelectedPlugins(self): |
|
873 """ |
|
874 Private slot to hide all selected plug-ins. |
|
875 """ |
|
876 hideList = [] |
|
877 for itm in self.__selectedItems(): |
|
878 pluginName = (itm.data(0, PluginRepositoryWidget.FilenameRole) |
|
879 .rsplit("-", 1)[0]) |
|
880 hideList.append(pluginName) |
|
881 self.__updateHiddenPluginsList(hideList) |
|
882 |
|
883 def __showAllPlugins(self): |
|
884 """ |
|
885 Private slot to show all plug-ins. |
|
886 """ |
|
887 self.__hiddenPlugins = [] |
|
888 self.__updateHiddenPluginsList([]) |
|
889 |
|
890 def __hasHiddenPlugins(self): |
|
891 """ |
|
892 Private method to check, if there are any hidden plug-ins. |
|
893 |
|
894 @return flag indicating the presence of hidden plug-ins (boolean) |
|
895 """ |
|
896 return bool(self.__hiddenPlugins) |
|
897 |
|
898 def __updateHiddenPluginsList(self, hideList): |
|
899 """ |
|
900 Private method to store the list of hidden plug-ins to the settings. |
|
901 |
|
902 @param hideList list of plug-ins to add to the list of hidden ones |
|
903 (list of string) |
|
904 """ |
|
905 if hideList: |
|
906 self.__hiddenPlugins.extend( |
|
907 [p for p in hideList if p not in self.__hiddenPlugins]) |
|
908 Preferences.setPluginManager("HiddenPlugins", self.__hiddenPlugins) |
|
909 self.__populateList() |
|
910 |
|
911 def __cleanupDownloads(self): |
|
912 """ |
|
913 Private slot to cleanup the plug-in downloads area. |
|
914 """ |
|
915 PluginRepositoryDownloadCleanup() |
|
916 |
|
917 |
|
918 class PluginRepositoryDialog(QDialog): |
|
919 """ |
|
920 Class for the dialog variant. |
|
921 """ |
|
922 def __init__(self, pluginManager, parent=None): |
|
923 """ |
|
924 Constructor |
|
925 |
|
926 @param pluginManager reference to the plugin manager object |
|
927 @type PluginManager |
|
928 @param parent reference to the parent widget |
|
929 @type QWidget |
|
930 """ |
|
931 super().__init__(parent) |
|
932 self.setSizeGripEnabled(True) |
|
933 |
|
934 self.__layout = QVBoxLayout(self) |
|
935 self.__layout.setContentsMargins(0, 0, 0, 0) |
|
936 self.setLayout(self.__layout) |
|
937 |
|
938 self.cw = PluginRepositoryWidget(pluginManager, parent=self) |
|
939 size = self.cw.size() |
|
940 self.__layout.addWidget(self.cw) |
|
941 self.resize(size) |
|
942 self.setWindowTitle(self.cw.windowTitle()) |
|
943 |
|
944 self.cw.buttonBox.accepted.connect(self.accept) |
|
945 self.cw.buttonBox.rejected.connect(self.reject) |
|
946 self.cw.closeAndInstall.connect(self.__closeAndInstall) |
|
947 |
|
948 def __closeAndInstall(self): |
|
949 """ |
|
950 Private slot to handle the closeAndInstall signal. |
|
951 """ |
|
952 self.done(QDialog.DialogCode.Accepted + 1) |
|
953 |
|
954 def getDownloadedPlugins(self): |
|
955 """ |
|
956 Public method to get the list of recently downloaded plugin files. |
|
957 |
|
958 @return list of plugin filenames (list of strings) |
|
959 """ |
|
960 return self.cw.getDownloadedPlugins() |
|
961 |
|
962 |
|
963 class PluginRepositoryWindow(EricMainWindow): |
|
964 """ |
|
965 Main window class for the standalone dialog. |
|
966 """ |
|
967 def __init__(self, parent=None): |
|
968 """ |
|
969 Constructor |
|
970 |
|
971 @param parent reference to the parent widget (QWidget) |
|
972 """ |
|
973 super().__init__(parent) |
|
974 self.cw = PluginRepositoryWidget(None, parent=self) |
|
975 size = self.cw.size() |
|
976 self.setCentralWidget(self.cw) |
|
977 self.resize(size) |
|
978 self.setWindowTitle(self.cw.windowTitle()) |
|
979 |
|
980 self.setStyle(Preferences.getUI("Style"), |
|
981 Preferences.getUI("StyleSheet")) |
|
982 |
|
983 self.cw.buttonBox.accepted.connect(self.close) |
|
984 self.cw.buttonBox.rejected.connect(self.close) |
|
985 self.cw.closeAndInstall.connect(self.__startPluginInstall) |
|
986 |
|
987 def __startPluginInstall(self): |
|
988 """ |
|
989 Private slot to start the eric plugin installation dialog. |
|
990 """ |
|
991 proc = QProcess() |
|
992 applPath = os.path.join(getConfig("ericDir"), "eric7_plugininstall.py") |
|
993 |
|
994 args = [] |
|
995 args.append(applPath) |
|
996 args += self.cw.getDownloadedPlugins() |
|
997 |
|
998 if ( |
|
999 not os.path.isfile(applPath) or |
|
1000 not proc.startDetached(Globals.getPythonExecutable(), args) |
|
1001 ): |
|
1002 EricMessageBox.critical( |
|
1003 self, |
|
1004 self.tr('Process Generation Error'), |
|
1005 self.tr( |
|
1006 '<p>Could not start the process.<br>' |
|
1007 'Ensure that it is available as <b>{0}</b>.</p>' |
|
1008 ).format(applPath), |
|
1009 self.tr('OK')) |
|
1010 |
|
1011 self.close() |
|
1012 |
|
1013 |
|
1014 def PluginRepositoryDownloadCleanup(quiet=False): |
|
1015 """ |
|
1016 Module function to clean up the plug-in downloads area. |
|
1017 |
|
1018 @param quiet flag indicating quiet operations |
|
1019 @type bool |
|
1020 """ |
|
1021 pluginsRegister = [] # list of plug-ins contained in the repository |
|
1022 |
|
1023 def registerPlugin(name, short, description, url, author, version, |
|
1024 filename, status): |
|
1025 """ |
|
1026 Method to register a plug-in's data. |
|
1027 |
|
1028 @param name data for the name field (string) |
|
1029 @param short data for the short field (string) |
|
1030 @param description data for the description field (list of strings) |
|
1031 @param url data for the url field (string) |
|
1032 @param author data for the author field (string) |
|
1033 @param version data for the version field (string) |
|
1034 @param filename data for the filename field (string) |
|
1035 @param status status of the plugin (string [stable, unstable, unknown]) |
|
1036 """ |
|
1037 pluginName = os.path.splitext(url.rsplit("/", 1)[1])[0] |
|
1038 if pluginName not in pluginsRegister: |
|
1039 pluginsRegister.append(pluginName) |
|
1040 |
|
1041 downloadPath = Preferences.getPluginManager("DownloadPath") |
|
1042 downloads = {} # plug-in name as key, file name as value |
|
1043 |
|
1044 # step 1: extract plug-ins and downloaded files |
|
1045 for pluginFile in os.listdir(downloadPath): |
|
1046 if not os.path.isfile(os.path.join(downloadPath, pluginFile)): |
|
1047 continue |
|
1048 |
|
1049 try: |
|
1050 pluginName, pluginVersion = ( |
|
1051 pluginFile.replace(".zip", "").rsplit("-", 1) |
|
1052 ) |
|
1053 pluginVersionList = re.split("[._-]", pluginVersion) |
|
1054 for index in range(len(pluginVersionList)): |
|
1055 try: |
|
1056 pluginVersionList[index] = int(pluginVersionList[index]) |
|
1057 except ValueError: |
|
1058 # use default of 0 |
|
1059 pluginVersionList[index] = 0 |
|
1060 except ValueError: |
|
1061 # rsplit() returned just one entry, i.e. file name doesn't contain |
|
1062 # version info separated by '-' |
|
1063 # => assume version 0.0.0 |
|
1064 pluginName = pluginFile.replace(".zip", "") |
|
1065 pluginVersionList = [0, 0, 0] |
|
1066 |
|
1067 if pluginName not in downloads: |
|
1068 downloads[pluginName] = [] |
|
1069 downloads[pluginName].append((pluginFile, tuple(pluginVersionList))) |
|
1070 |
|
1071 # step 2: delete old entries |
|
1072 hiddenPlugins = Preferences.getPluginManager("HiddenPlugins") |
|
1073 for pluginName in downloads: |
|
1074 downloads[pluginName].sort(key=lambda x: x[1]) |
|
1075 |
|
1076 removeFiles = ( |
|
1077 [f[0] for f in downloads[pluginName]] |
|
1078 if (pluginName in hiddenPlugins and |
|
1079 not Preferences.getPluginManager("KeepHidden")) else |
|
1080 [f[0] for f in downloads[pluginName][ |
|
1081 :-Preferences.getPluginManager("KeepGenerations")]] |
|
1082 ) |
|
1083 for removeFile in removeFiles: |
|
1084 try: |
|
1085 os.remove(os.path.join(downloadPath, removeFile)) |
|
1086 except OSError as err: |
|
1087 if not quiet: |
|
1088 EricMessageBox.critical( |
|
1089 None, |
|
1090 QCoreApplication.translate( |
|
1091 "PluginRepositoryWidget", |
|
1092 "Cleanup of Plugin Downloads"), |
|
1093 QCoreApplication.translate( |
|
1094 "PluginRepositoryWidget", |
|
1095 """<p>The plugin download <b>{0}</b> could""" |
|
1096 """ not be deleted.</p><p>Reason: {1}</p>""") |
|
1097 .format(removeFile, str(err))) |
|
1098 |
|
1099 # step 3: delete entries of obsolete plug-ins |
|
1100 pluginRepositoryFile = os.path.join(Utilities.getConfigDir(), |
|
1101 "PluginRepository") |
|
1102 if os.path.exists(pluginRepositoryFile): |
|
1103 f = QFile(pluginRepositoryFile) |
|
1104 if f.open(QIODevice.OpenModeFlag.ReadOnly): |
|
1105 from EricXML.PluginRepositoryReader import PluginRepositoryReader |
|
1106 reader = PluginRepositoryReader(f, registerPlugin) |
|
1107 reader.readXML() |
|
1108 |
|
1109 for pluginName in downloads: |
|
1110 if pluginName not in pluginsRegister: |
|
1111 removeFiles = [f[0] for f in downloads[pluginName]] |
|
1112 for removeFile in removeFiles: |
|
1113 try: |
|
1114 os.remove(os.path.join(downloadPath, removeFile)) |
|
1115 except OSError as err: |
|
1116 if not quiet: |
|
1117 EricMessageBox.critical( |
|
1118 None, |
|
1119 QCoreApplication.translate( |
|
1120 "PluginRepositoryWidget", |
|
1121 "Cleanup of Plugin Downloads"), |
|
1122 QCoreApplication.translate( |
|
1123 "PluginRepositoryWidget", |
|
1124 "<p>The plugin download <b>{0}</b>" |
|
1125 " could not be deleted.</p>" |
|
1126 "<p>Reason: {1}</p>""") |
|
1127 .format(removeFile, str(err))) |