10313:8f69edb4ad73 | 10314:1f7d52f024b1 |
---|---|
5 | 5 |
6 """ | 6 """ |
7 Module implementing a dialog showing the available plugins. | 7 Module implementing a dialog showing the available plugins. |
8 """ | 8 """ |
9 | 9 |
10 import enum | |
10 import glob | 11 import glob |
11 import os | 12 import os |
12 import re | 13 import re |
13 import zipfile | 14 import zipfile |
15 | |
16 from collections import ChainMap, defaultdict | |
14 | 17 |
15 from PyQt6.QtCore import ( | 18 from PyQt6.QtCore import ( |
16 QCoreApplication, | 19 QCoreApplication, |
17 QFile, | 20 QFile, |
18 QIODevice, | 21 QIODevice, |
64 | 67 |
65 from .PluginManager import PluginManager | 68 from .PluginManager import PluginManager |
66 from .Ui_PluginRepositoryDialog import Ui_PluginRepositoryDialog | 69 from .Ui_PluginRepositoryDialog import Ui_PluginRepositoryDialog |
67 | 70 |
68 | 71 |
72 class PluginStatus(enum.Enum): | |
73 """ | |
74 Class defining the various plugin status. | |
75 """ | |
76 | |
77 UpToDate = 0 | |
78 New = 1 | |
79 LocalUpdate = 2 | |
80 RemoteUpdate = 3 | |
81 Error = 4 | |
82 | |
83 | |
69 class PluginRepositoryWidget(QWidget, Ui_PluginRepositoryDialog): | 84 class PluginRepositoryWidget(QWidget, Ui_PluginRepositoryDialog): |
70 """ | 85 """ |
71 Class implementing a dialog showing the available plugins. | 86 Class implementing a dialog showing the available plugins. |
72 | 87 |
73 @signal closeAndInstall() emitted when the Close & Install button is | 88 @signal closeAndInstall() emitted when the Close & Install button is |
78 | 93 |
79 DescrRole = Qt.ItemDataRole.UserRole | 94 DescrRole = Qt.ItemDataRole.UserRole |
80 UrlRole = Qt.ItemDataRole.UserRole + 1 | 95 UrlRole = Qt.ItemDataRole.UserRole + 1 |
81 FilenameRole = Qt.ItemDataRole.UserRole + 2 | 96 FilenameRole = Qt.ItemDataRole.UserRole + 2 |
82 AuthorRole = Qt.ItemDataRole.UserRole + 3 | 97 AuthorRole = Qt.ItemDataRole.UserRole + 3 |
83 | |
84 PluginStatusUpToDate = 0 | |
85 PluginStatusNew = 1 | |
86 PluginStatusLocalUpdate = 2 | |
87 PluginStatusRemoteUpdate = 3 | |
88 PluginStatusError = 4 | |
89 | 98 |
90 def __init__(self, pluginManager, integrated=False, parent=None): | 99 def __init__(self, pluginManager, integrated=False, parent=None): |
91 """ | 100 """ |
92 Constructor | 101 Constructor |
93 | 102 |
109 self.__pluginManager = pluginManager | 118 self.__pluginManager = pluginManager |
110 self.__external = False | 119 self.__external = False |
111 | 120 |
112 self.__integratedWidget = integrated | 121 self.__integratedWidget = integrated |
113 | 122 |
123 self.__statusTranslations = { | |
124 "stable": self.tr("Stable"), | |
125 "unstable": self.tr("Unstable"), | |
126 "obsolete": self.tr("Obsolete"), | |
127 "unknown": self.tr("Unknown"), | |
128 } | |
129 self.__initHeaderItemsCache() | |
130 | |
114 if self.__integratedWidget: | 131 if self.__integratedWidget: |
115 self.layout().setContentsMargins(0, 3, 0, 0) | 132 self.layout().setContentsMargins(0, 3, 0, 0) |
116 | 133 |
117 self.__actionButtonsLayout = QHBoxLayout() | 134 self.__actionButtonsLayout = QHBoxLayout() |
118 self.__actionButtonsLayout.addStretch() | 135 self.__actionButtonsLayout.addStretch() |
270 @pyqtSlot(QAbstractButton) | 287 @pyqtSlot(QAbstractButton) |
271 def on_buttonBox_clicked(self, button): | 288 def on_buttonBox_clicked(self, button): |
272 """ | 289 """ |
273 Private slot to handle the click of a button of the button box. | 290 Private slot to handle the click of a button of the button box. |
274 | 291 |
275 @param button reference to the button pressed (QAbstractButton) | 292 @param button reference to the button pressed |
293 @type QAbstractButton | |
276 """ | 294 """ |
277 if button == self.__updateButton: | 295 if button == self.__updateButton: |
278 self.__updateList() | 296 self.__updateList() |
279 elif button == self.__downloadButton: | 297 elif button == self.__downloadButton: |
280 self.__downloadButtonClicked() | 298 self.__downloadButtonClicked() |
304 | 322 |
305 def __formatDescription(self, lines): | 323 def __formatDescription(self, lines): |
306 """ | 324 """ |
307 Private method to format the description. | 325 Private method to format the description. |
308 | 326 |
309 @param lines lines of the description (list of strings) | 327 @param lines lines of the description |
310 @return formatted description (string) | 328 @type list of str |
329 @return formatted description | |
330 @rtype str | |
311 """ | 331 """ |
312 # remove empty line at start and end | 332 # remove empty line at start and end |
313 newlines = lines[:] | 333 newlines = lines[:] |
314 if len(newlines) and newlines[0] == "": | 334 if len(newlines) and newlines[0] == "": |
315 del newlines[0] | 335 del newlines[0] |
331 Private method to change the scheme of the given URL. | 351 Private method to change the scheme of the given URL. |
332 | 352 |
333 @param url URL to be modified | 353 @param url URL to be modified |
334 @type str | 354 @type str |
335 @param newScheme scheme to be set for the given URL | 355 @param newScheme scheme to be set for the given URL |
356 @type str | |
336 @return modified URL | 357 @return modified URL |
337 @rtype str | 358 @rtype str |
338 """ | 359 """ |
339 if not newScheme: | 360 if not newScheme: |
340 newScheme = ( | 361 newScheme = ( |
348 @pyqtSlot(QPoint) | 369 @pyqtSlot(QPoint) |
349 def on_repositoryList_customContextMenuRequested(self, pos): | 370 def on_repositoryList_customContextMenuRequested(self, pos): |
350 """ | 371 """ |
351 Private slot to show the context menu. | 372 Private slot to show the context menu. |
352 | 373 |
353 @param pos position to show the menu (QPoint) | 374 @param pos position to show the menu |
375 @type QPoint | |
354 """ | 376 """ |
355 self.__hideAct.setEnabled( | 377 self.__hideAct.setEnabled( |
356 self.repositoryList.currentItem() is not None | 378 self.repositoryList.currentItem() is not None |
357 and len(self.__selectedItems()) == 1 | 379 and len(self.__selectedItems()) == 1 |
358 ) | 380 ) |
363 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) | 385 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) |
364 def on_repositoryList_currentItemChanged(self, current, previous): | 386 def on_repositoryList_currentItemChanged(self, current, previous): |
365 """ | 387 """ |
366 Private slot to handle the change of the current item. | 388 Private slot to handle the change of the current item. |
367 | 389 |
368 @param current reference to the new current item (QTreeWidgetItem) | 390 @param current reference to the new current item |
369 @param previous reference to the old current item (QTreeWidgetItem) | 391 @type QTreeWidgetItem |
392 @param previous reference to the old current item | |
393 @type QTreeWidgetItem | |
370 """ | 394 """ |
371 if self.__repositoryMissing or current is None: | 395 if self.__repositoryMissing or current is None: |
372 self.descriptionEdit.clear() | 396 self.descriptionEdit.clear() |
373 self.authorEdit.clear() | 397 self.authorEdit.clear() |
374 return | 398 return |
387 current.data(0, PluginRepositoryWidget.AuthorRole) or "" | 411 current.data(0, PluginRepositoryWidget.AuthorRole) or "" |
388 ) | 412 ) |
389 | 413 |
390 def __selectedItems(self): | 414 def __selectedItems(self): |
391 """ | 415 """ |
392 Private method to get all selected items without the toplevel ones. | 416 Private method to get all selected items without the status and category items. |
393 | 417 |
394 @return list of selected items (list) | 418 @return list of selected items without header items |
395 """ | 419 @rtype list of QTreeWidgetItem |
396 ql = self.repositoryList.selectedItems() | 420 """ |
397 for index in range(self.repositoryList.topLevelItemCount()): | 421 selectedItems = [] |
398 ti = self.repositoryList.topLevelItem(index) | 422 allCategoryItems = ChainMap(*self.__categoryItems.values()) |
399 if ti in ql: | 423 for itm in self.repositoryList.selectedItems(): |
400 ql.remove(ti) | 424 if ( |
401 return ql | 425 itm not in self.__statusItems.values() |
426 and itm not in allCategoryItems.values() | |
427 ): | |
428 selectedItems.append(itm) | |
429 return selectedItems | |
402 | 430 |
403 @pyqtSlot() | 431 @pyqtSlot() |
404 def on_repositoryList_itemSelectionChanged(self): | 432 def on_repositoryList_itemSelectionChanged(self): |
405 """ | 433 """ |
406 Private slot to handle a change of the selection. | 434 Private slot to handle a change of the selection. |
424 url = self.repositoryUrlEdit.text() | 452 url = self.repositoryUrlEdit.text() |
425 if Preferences.getPluginManager("ForceHttpPluginDownload"): | 453 if Preferences.getPluginManager("ForceHttpPluginDownload"): |
426 url = url.replace("https://", "http://") | 454 url = url.replace("https://", "http://") |
427 self.__pluginManager.downLoadRepositoryFile(url=url) | 455 self.__pluginManager.downLoadRepositoryFile(url=url) |
428 | 456 |
429 def __downloadRepositoryFileDone(self, status, filename): # noqa: U100 | |
430 """ | |
431 Private method called after the repository file was downloaded. | |
432 | |
433 @param status flaging indicating a successful download (boolean) | |
434 @param filename full path of the downloaded file (string) | |
435 """ | |
436 self.__populateList() | |
437 | |
438 def __downloadPluginDone(self, status, filename): | 457 def __downloadPluginDone(self, status, filename): |
439 """ | 458 """ |
440 Private method called, when the download of a plugin is finished. | 459 Private method called, when the download of a plugin is finished. |
441 | 460 |
442 @param status flag indicating a successful download (boolean) | 461 @param status flag indicating a successful download |
443 @param filename full path of the downloaded file (string) | 462 @type bool |
463 @param filename full path of the downloaded file | |
464 @type str | |
444 """ | 465 """ |
445 if status: | 466 if status: |
446 self.__pluginsDownloaded.append(filename) | 467 self.__pluginsDownloaded.append(filename) |
447 if self.__isDownloadInstall: | 468 if self.__isDownloadInstall: |
448 self.__allDownloadedOk &= status | 469 self.__allDownloadedOk &= status |
478 newScheme = ( | 499 newScheme = ( |
479 "http:" | 500 "http:" |
480 if Preferences.getPluginManager("ForceHttpPluginDownload") | 501 if Preferences.getPluginManager("ForceHttpPluginDownload") |
481 else self.repositoryUrlEdit.text().split("//", 1)[0] | 502 else self.repositoryUrlEdit.text().split("//", 1)[0] |
482 ) | 503 ) |
483 for itm in self.repositoryList.selectedItems(): | 504 for itm in self.__selectedItems(): |
484 if itm not in [ | 505 url = self.__changeScheme( |
485 self.__stableItem, | 506 itm.data(0, PluginRepositoryWidget.UrlRole), newScheme |
486 self.__unstableItem, | 507 ) |
487 self.__unknownItem, | 508 filename = os.path.join( |
488 self.__obsoleteItem, | 509 Preferences.getPluginManager("DownloadPath"), |
489 ]: | 510 itm.data(0, PluginRepositoryWidget.FilenameRole), |
490 url = self.__changeScheme( | 511 ) |
491 itm.data(0, PluginRepositoryWidget.UrlRole), newScheme | 512 self.__pluginsToDownload.append((url, filename)) |
492 ) | |
493 filename = os.path.join( | |
494 Preferences.getPluginManager("DownloadPath"), | |
495 itm.data(0, PluginRepositoryWidget.FilenameRole), | |
496 ) | |
497 self.__pluginsToDownload.append((url, filename)) | |
498 if self.__pluginsToDownload: | 513 if self.__pluginsToDownload: |
499 self.__downloadPlugin() | 514 self.__downloadPlugin() |
500 | 515 |
501 def __downloadPluginsDone(self): | 516 def __downloadPluginsDone(self): |
502 """ | 517 """ |
530 | 545 |
531 def __resortRepositoryList(self): | 546 def __resortRepositoryList(self): |
532 """ | 547 """ |
533 Private method to resort the tree. | 548 Private method to resort the tree. |
534 """ | 549 """ |
535 self.repositoryList.sortItems( | 550 if self.__integratedWidget: |
536 self.repositoryList.sortColumn(), | 551 self.repositoryList.sortItems( |
537 self.repositoryList.header().sortIndicatorOrder(), | 552 self.repositoryList.sortColumn(), |
538 ) | 553 Qt.SortOrder.AscendingOrder, |
554 ) | |
555 else: | |
556 self.repositoryList.sortItems( | |
557 self.repositoryList.sortColumn(), | |
558 self.repositoryList.header().sortIndicatorOrder(), | |
559 ) | |
560 | |
561 def __initHeaderItemsCache(self): | |
562 """ | |
563 Private method to initialize the cache variables for the header items. | |
564 """ | |
565 self.__statusItems = defaultdict(lambda: None) | |
566 self.__categoryItems = defaultdict(dict) | |
539 | 567 |
540 def __populateList(self): | 568 def __populateList(self): |
541 """ | 569 """ |
542 Private method to populate the list of available plugins. | 570 Private method to populate the list of available plugins. |
543 """ | 571 """ |
572 self.__initHeaderItemsCache() | |
544 self.repositoryList.clear() | 573 self.repositoryList.clear() |
545 self.__stableItem = None | |
546 self.__unstableItem = None | |
547 self.__unknownItem = None | |
548 self.__obsoleteItem = None | |
549 | 574 |
550 self.__newItems = 0 | 575 self.__newItems = 0 |
551 self.__updateLocalItems = 0 | 576 self.__updateLocalItems = 0 |
552 self.__updateRemoteItems = 0 | 577 self.__updateRemoteItems = 0 |
553 | 578 |
602 | 627 |
603 def __downloadFile(self, url, filename, doneMethod=None): | 628 def __downloadFile(self, url, filename, doneMethod=None): |
604 """ | 629 """ |
605 Private slot to download the given file. | 630 Private slot to download the given file. |
606 | 631 |
607 @param url URL for the download (string) | 632 @param url URL for the download |
608 @param filename local name of the file (string) | 633 @type str |
634 @param filename local name of the file | |
635 @type str | |
609 @param doneMethod method to be called when done | 636 @param doneMethod method to be called when done |
637 @type function | |
610 """ | 638 """ |
611 if self.__online: | 639 if self.__online: |
612 self.__updateButton.setEnabled(False) | 640 self.__updateButton.setEnabled(False) |
613 self.__downloadButton.setEnabled(False) | 641 self.__downloadButton.setEnabled(False) |
614 self.__downloadInstallButton.setEnabled(False) | 642 self.__downloadInstallButton.setEnabled(False) |
650 @param reply reference to the reply object of the download | 678 @param reply reference to the reply object of the download |
651 @type QNetworkReply | 679 @type QNetworkReply |
652 @param fileName local name of the file | 680 @param fileName local name of the file |
653 @type str | 681 @type str |
654 @param doneMethod method to be called when done | 682 @param doneMethod method to be called when done |
655 @type func | 683 @type function |
656 """ | 684 """ |
657 self.__updateButton.setEnabled(True) | 685 self.__updateButton.setEnabled(True) |
658 if not self.__integratedWidget: | 686 if not self.__integratedWidget: |
659 self.__closeButton.setEnabled(True) | 687 self.__closeButton.setEnabled(True) |
660 self.__downloadCancelButton.setEnabled(False) | 688 self.__downloadCancelButton.setEnabled(False) |
718 | 746 |
719 def __downloadProgress(self, done, total): | 747 def __downloadProgress(self, done, total): |
720 """ | 748 """ |
721 Private slot to show the download progress. | 749 Private slot to show the download progress. |
722 | 750 |
723 @param done number of bytes downloaded so far (integer) | 751 @param done number of bytes downloaded so far |
724 @param total total bytes to be downloaded (integer) | 752 @type int |
753 @param total total bytes to be downloaded | |
754 @type int | |
725 """ | 755 """ |
726 if total: | 756 if total: |
727 self.downloadProgress.setMaximum(total) | 757 self.downloadProgress.setMaximum(total) |
728 self.downloadProgress.setValue(done) | 758 self.downloadProgress.setValue(done) |
729 | 759 |
730 def addEntry( | 760 def addEntry( |
731 self, name, short, description, url, author, version, filename, status | 761 self, |
762 name, | |
763 short, | |
764 description, | |
765 url, | |
766 author, | |
767 version, | |
768 filename, | |
769 status, | |
770 category, | |
732 ): | 771 ): |
733 """ | 772 """ |
734 Public method to add an entry to the list. | 773 Public method to add an entry to the list. |
735 | 774 |
736 @param name data for the name field (string) | 775 @param name data for the name field |
737 @param short data for the short field (string) | 776 @type str |
738 @param description data for the description field (list of strings) | 777 @param short data for the short field |
739 @param url data for the url field (string) | 778 @type str |
740 @param author data for the author field (string) | 779 @param description data for the description field |
741 @param version data for the version field (string) | 780 @type list of str |
742 @param filename data for the filename field (string) | 781 @param url data for the url field |
743 @param status status of the plugin (string [stable, unstable, unknown]) | 782 @type str |
783 @param author data for the author field | |
784 @type str | |
785 @param version data for the version field | |
786 @type str | |
787 @param filename data for the filename field | |
788 @type str | |
789 @param status status of the plugin (one of stable, unstable, unknown) | |
790 @type str | |
791 @param category category designation of the plugin | |
792 @type str | |
744 """ | 793 """ |
745 pluginName = filename.rsplit("-", 1)[0] | 794 pluginName = filename.rsplit("-", 1)[0] |
746 if pluginName in self.__hiddenPlugins: | 795 if pluginName in self.__hiddenPlugins: |
747 return | 796 return |
748 | 797 |
749 if status == "stable": | 798 # 1. determine and create the status item |
750 if self.__stableItem is None: | 799 statusItem = self.__statusItems[status] |
751 self.__stableItem = QTreeWidgetItem( | 800 if statusItem is None: |
752 self.repositoryList, [self.tr("Stable")] | 801 statusItem = QTreeWidgetItem( |
753 ) | 802 self.repositoryList, |
754 self.__stableItem.setExpanded(True) | 803 [ |
755 parent = self.__stableItem | 804 self.__statusTranslations.get( |
756 elif status == "unstable": | 805 status, self.__statusTranslations["unknown"] |
757 if self.__unstableItem is None: | 806 ) |
758 self.__unstableItem = QTreeWidgetItem( | 807 ], |
759 self.repositoryList, [self.tr("Unstable")] | 808 ) |
760 ) | 809 statusItem.setExpanded(True) |
761 self.__unstableItem.setExpanded(True) | 810 statusItem.setFirstColumnSpanned(True) |
762 parent = self.__unstableItem | 811 self.__statusItems[status] = statusItem |
763 elif status == "obsolete": | 812 |
764 if self.__obsoleteItem is None: | 813 # 2. determine and create the category item |
765 self.__obsoleteItem = QTreeWidgetItem( | 814 try: |
766 self.repositoryList, [self.tr("Obsolete")] | 815 categoryItem = self.__categoryItems[status][category] |
767 ) | 816 except KeyError: |
768 self.__obsoleteItem.setExpanded(True) | 817 # create the category item |
769 parent = self.__obsoleteItem | 818 categoryItem = QTreeWidgetItem(statusItem, [category]) |
770 else: | 819 categoryItem.setExpanded(True) |
771 if self.__unknownItem is None: | 820 categoryItem.setFirstColumnSpanned(True) |
772 self.__unknownItem = QTreeWidgetItem( | 821 self.__categoryItems[status][category] = categoryItem |
773 self.repositoryList, [self.tr("Unknown")] | 822 |
774 ) | 823 # 3. create the plugin item |
775 self.__unknownItem.setExpanded(True) | |
776 parent = self.__unknownItem | |
777 | |
778 if self.__integratedWidget: | 824 if self.__integratedWidget: |
779 entryFormat = "<b>{0}</b> - Version: <i>{1}</i><br/>{2}" | 825 entryFormat = "<b>{0}</b> - Version: <i>{1}</i><br/>{2}" |
780 itm = QTreeWidgetItem(parent) | 826 itm = QTreeWidgetItem(categoryItem) |
781 itm.setFirstColumnSpanned(True) | 827 itm.setFirstColumnSpanned(True) |
782 label = QLabel(entryFormat.format(name, version, short)) | 828 label = QLabel(entryFormat.format(name, version, short)) |
783 self.repositoryList.setItemWidget(itm, 0, label) | 829 self.repositoryList.setItemWidget(itm, 0, label) |
784 else: | 830 else: |
785 itm = QTreeWidgetItem(parent, [name, version, short]) | 831 itm = QTreeWidgetItem(categoryItem, [name, version, short]) |
786 | 832 |
787 itm.setData(0, PluginRepositoryWidget.UrlRole, url) | 833 itm.setData(0, PluginRepositoryWidget.UrlRole, url) |
788 itm.setData(0, PluginRepositoryWidget.FilenameRole, filename) | 834 itm.setData(0, PluginRepositoryWidget.FilenameRole, filename) |
789 itm.setData(0, PluginRepositoryWidget.AuthorRole, author) | 835 itm.setData(0, PluginRepositoryWidget.AuthorRole, author) |
790 itm.setData(0, PluginRepositoryWidget.DescrRole, description) | 836 itm.setData(0, PluginRepositoryWidget.DescrRole, description) |
791 | 837 |
792 iconColumn = 0 if self.__integratedWidget else 1 | 838 iconColumn = 0 if self.__integratedWidget else 1 |
793 updateStatus = self.__updateStatus(filename, version) | 839 updateStatus = self.__updateStatus(filename, version) |
794 if updateStatus == PluginRepositoryWidget.PluginStatusUpToDate: | 840 if updateStatus == PluginStatus.UpToDate: |
795 itm.setIcon(iconColumn, EricPixmapCache.getIcon("empty")) | 841 itm.setIcon(iconColumn, EricPixmapCache.getIcon("empty")) |
796 itm.setToolTip(iconColumn, self.tr("up-to-date")) | 842 itm.setToolTip(iconColumn, self.tr("up-to-date")) |
797 elif updateStatus == PluginRepositoryWidget.PluginStatusNew: | 843 elif updateStatus == PluginStatus.New: |
798 itm.setIcon(iconColumn, EricPixmapCache.getIcon("download")) | 844 itm.setIcon(iconColumn, EricPixmapCache.getIcon("download")) |
799 itm.setToolTip(iconColumn, self.tr("new download available")) | 845 itm.setToolTip(iconColumn, self.tr("new download available")) |
800 self.__newItems += 1 | 846 self.__newItems += 1 |
801 elif updateStatus == PluginRepositoryWidget.PluginStatusLocalUpdate: | 847 elif updateStatus == PluginStatus.LocalUpdate: |
802 itm.setIcon(iconColumn, EricPixmapCache.getIcon("updateLocal")) | 848 itm.setIcon(iconColumn, EricPixmapCache.getIcon("updateLocal")) |
803 itm.setToolTip(iconColumn, self.tr("update installable")) | 849 itm.setToolTip(iconColumn, self.tr("update installable")) |
804 self.__updateLocalItems += 1 | 850 self.__updateLocalItems += 1 |
805 elif updateStatus == PluginRepositoryWidget.PluginStatusRemoteUpdate: | 851 elif updateStatus == PluginStatus.RemoteUpdate: |
806 itm.setIcon(iconColumn, EricPixmapCache.getIcon("updateRemote")) | 852 itm.setIcon(iconColumn, EricPixmapCache.getIcon("updateRemote")) |
807 itm.setToolTip(iconColumn, self.tr("updated download available")) | 853 itm.setToolTip(iconColumn, self.tr("updated download available")) |
808 self.__updateRemoteItems += 1 | 854 self.__updateRemoteItems += 1 |
809 elif updateStatus == PluginRepositoryWidget.PluginStatusError: | 855 elif updateStatus == PluginStatus.Error: |
810 itm.setIcon(iconColumn, EricPixmapCache.getIcon("warning")) | 856 itm.setIcon(iconColumn, EricPixmapCache.getIcon("warning")) |
811 itm.setToolTip(iconColumn, self.tr("error determining status")) | 857 itm.setToolTip(iconColumn, self.tr("error determining status")) |
812 | 858 |
813 def __updateStatus(self, filename, version): | 859 def __updateStatus(self, filename, version): |
814 """ | 860 """ |
815 Private method to check the given archive update status. | 861 Private method to check the given archive update status. |
816 | 862 |
817 @param filename data for the filename field (string) | 863 @param filename data for the filename field |
818 @param version data for the version field (string) | 864 @type str |
819 @return plug-in update status (integer, one of PluginStatusNew, | 865 @param version data for the version field |
820 PluginStatusUpToDate, PluginStatusLocalUpdate, | 866 @type str |
821 PluginStatusRemoteUpdate) | 867 @return plug-in update status |
868 @rtype int (one of PluginStatusNew, PluginStatusUpToDate, | |
869 PluginStatusLocalUpdate, PluginStatusRemoteUpdate) | |
822 """ | 870 """ |
823 archive = os.path.join(Preferences.getPluginManager("DownloadPath"), filename) | 871 archive = os.path.join(Preferences.getPluginManager("DownloadPath"), filename) |
824 | 872 |
825 # check, if it is an update (i.e. we already have archives | 873 # check, if it is an update (i.e. we already have archives |
826 # with the same pattern) | 874 # with the same pattern) |
828 if len(glob.glob(archivesPattern)) == 0: | 876 if len(glob.glob(archivesPattern)) == 0: |
829 # Check against installed/loaded plug-ins | 877 # Check against installed/loaded plug-ins |
830 pluginName = filename.rsplit("-", 1)[0] | 878 pluginName = filename.rsplit("-", 1)[0] |
831 pluginDetails = self.__pluginManager.getPluginDetails(pluginName) | 879 pluginDetails = self.__pluginManager.getPluginDetails(pluginName) |
832 if pluginDetails is None or pluginDetails["moduleName"] != pluginName: | 880 if pluginDetails is None or pluginDetails["moduleName"] != pluginName: |
833 return PluginRepositoryWidget.PluginStatusNew | 881 return PluginStatus.New |
834 if pluginDetails["error"]: | 882 if pluginDetails["error"]: |
835 return PluginRepositoryWidget.PluginStatusError | 883 return PluginStatus.Error |
836 pluginVersionTuple = Globals.versionToTuple(pluginDetails["version"])[:3] | 884 pluginVersionTuple = Globals.versionToTuple(pluginDetails["version"])[:3] |
837 versionTuple = Globals.versionToTuple(version)[:3] | 885 versionTuple = Globals.versionToTuple(version)[:3] |
838 if pluginVersionTuple < versionTuple: | 886 if pluginVersionTuple < versionTuple: |
839 return PluginRepositoryWidget.PluginStatusRemoteUpdate | 887 return PluginStatus.RemoteUpdate |
840 else: | 888 else: |
841 return PluginRepositoryWidget.PluginStatusUpToDate | 889 return PluginStatus.UpToDate |
842 | 890 |
843 # check, if the archive exists | 891 # check, if the archive exists |
844 if not os.path.exists(archive): | 892 if not os.path.exists(archive): |
845 return PluginRepositoryWidget.PluginStatusRemoteUpdate | 893 return PluginStatus.RemoteUpdate |
846 | 894 |
847 # check, if the archive is a valid zip file | 895 # check, if the archive is a valid zip file |
848 if not zipfile.is_zipfile(archive): | 896 if not zipfile.is_zipfile(archive): |
849 return PluginRepositoryWidget.PluginStatusRemoteUpdate | 897 return PluginStatus.RemoteUpdate |
850 | 898 |
851 zipFile = zipfile.ZipFile(archive, "r") | 899 zipFile = zipfile.ZipFile(archive, "r") |
852 try: | 900 try: |
853 aversion = zipFile.read("VERSION").decode("utf-8") | 901 aversion = zipFile.read("VERSION").decode("utf-8") |
854 except KeyError: | 902 except KeyError: |
858 if aversion == version: | 906 if aversion == version: |
859 # Check against installed/loaded plug-ins | 907 # Check against installed/loaded plug-ins |
860 pluginName = filename.rsplit("-", 1)[0] | 908 pluginName = filename.rsplit("-", 1)[0] |
861 pluginDetails = self.__pluginManager.getPluginDetails(pluginName) | 909 pluginDetails = self.__pluginManager.getPluginDetails(pluginName) |
862 if pluginDetails is None: | 910 if pluginDetails is None: |
863 return PluginRepositoryWidget.PluginStatusLocalUpdate | 911 return PluginStatus.LocalUpdate |
864 if ( | 912 if ( |
865 Globals.versionToTuple(pluginDetails["version"])[:3] | 913 Globals.versionToTuple(pluginDetails["version"])[:3] |
866 < Globals.versionToTuple(version)[:3] | 914 < Globals.versionToTuple(version)[:3] |
867 ): | 915 ): |
868 return PluginRepositoryWidget.PluginStatusLocalUpdate | 916 return PluginStatus.LocalUpdate |
869 else: | 917 else: |
870 return PluginRepositoryWidget.PluginStatusUpToDate | 918 return PluginStatus.UpToDate |
871 else: | 919 else: |
872 return PluginRepositoryWidget.PluginStatusRemoteUpdate | 920 return PluginStatus.RemoteUpdate |
873 | 921 |
874 def __sslErrors(self, reply, errors): | 922 def __sslErrors(self, reply, errors): |
875 """ | 923 """ |
876 Private slot to handle SSL errors. | 924 Private slot to handle SSL errors. |
877 | 925 |
878 @param reply reference to the reply object (QNetworkReply) | 926 @param reply reference to the reply object |
879 @param errors list of SSL errors (list of QSslError) | 927 @type QNetworkReply |
928 @param errors list of SSL errors | |
929 @type list of QSslError | |
880 """ | 930 """ |
881 ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0] | 931 ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0] |
882 if ignored == EricSslErrorState.NOT_IGNORED: | 932 if ignored == EricSslErrorState.NOT_IGNORED: |
883 self.__downloadCancel(reply) | 933 self.__downloadCancel(reply) |
884 | 934 |
885 def getDownloadedPlugins(self): | 935 def getDownloadedPlugins(self): |
886 """ | 936 """ |
887 Public method to get the list of recently downloaded plugin files. | 937 Public method to get the list of recently downloaded plugin files. |
888 | 938 |
889 @return list of plugin filenames (list of strings) | 939 @return list of plugin filenames |
940 @rtype list of str | |
890 """ | 941 """ |
891 return self.__pluginsDownloaded | 942 return self.__pluginsDownloaded |
892 | 943 |
893 @pyqtSlot(bool) | 944 @pyqtSlot(bool) |
894 def on_repositoryUrlEditButton_toggled(self, checked): | 945 def on_repositoryUrlEditButton_toggled(self, checked): |
895 """ | 946 """ |
896 Private slot to set the read only status of the repository URL line | 947 Private slot to set the read only status of the repository URL line |
897 edit. | 948 edit. |
898 | 949 |
899 @param checked state of the push button (boolean) | 950 @param checked state of the push button |
951 @type bool | |
900 """ | 952 """ |
901 self.repositoryUrlEdit.setReadOnly(not checked) | 953 self.repositoryUrlEdit.setReadOnly(not checked) |
902 | 954 |
903 def __closeAndInstall(self): | 955 def __closeAndInstall(self): |
904 """ | 956 """ |
942 | 994 |
943 def __hasHiddenPlugins(self): | 995 def __hasHiddenPlugins(self): |
944 """ | 996 """ |
945 Private method to check, if there are any hidden plug-ins. | 997 Private method to check, if there are any hidden plug-ins. |
946 | 998 |
947 @return flag indicating the presence of hidden plug-ins (boolean) | 999 @return flag indicating the presence of hidden plug-ins |
1000 @rtype bool | |
948 """ | 1001 """ |
949 return bool(self.__hiddenPlugins) | 1002 return bool(self.__hiddenPlugins) |
950 | 1003 |
951 def __updateHiddenPluginsList(self, hideList): | 1004 def __updateHiddenPluginsList(self, hideList): |
952 """ | 1005 """ |
953 Private method to store the list of hidden plug-ins to the settings. | 1006 Private method to store the list of hidden plug-ins to the settings. |
954 | 1007 |
955 @param hideList list of plug-ins to add to the list of hidden ones | 1008 @param hideList list of plug-ins to add to the list of hidden ones |
956 (list of string) | 1009 @type list of str |
957 """ | 1010 """ |
958 if hideList: | 1011 if hideList: |
959 self.__hiddenPlugins.extend( | 1012 self.__hiddenPlugins.extend( |
960 [p for p in hideList if p not in self.__hiddenPlugins] | 1013 [p for p in hideList if p not in self.__hiddenPlugins] |
961 ) | 1014 ) |
1008 | 1061 |
1009 def getDownloadedPlugins(self): | 1062 def getDownloadedPlugins(self): |
1010 """ | 1063 """ |
1011 Public method to get the list of recently downloaded plugin files. | 1064 Public method to get the list of recently downloaded plugin files. |
1012 | 1065 |
1013 @return list of plugin filenames (list of strings) | 1066 @return list of plugin filenames |
1067 @rtype list of str | |
1014 """ | 1068 """ |
1015 return self.cw.getDownloadedPlugins() | 1069 return self.cw.getDownloadedPlugins() |
1016 | 1070 |
1017 | 1071 |
1018 class PluginRepositoryWindow(EricMainWindow): | 1072 class PluginRepositoryWindow(EricMainWindow): |
1022 | 1076 |
1023 def __init__(self, parent=None): | 1077 def __init__(self, parent=None): |
1024 """ | 1078 """ |
1025 Constructor | 1079 Constructor |
1026 | 1080 |
1027 @param parent reference to the parent widget (QWidget) | 1081 @param parent reference to the parent widget |
1082 @type QWidget | |
1028 """ | 1083 """ |
1029 super().__init__(parent) | 1084 super().__init__(parent) |
1030 self.cw = PluginRepositoryWidget(None, parent=self) | 1085 self.cw = PluginRepositoryWidget(None, parent=self) |
1031 size = self.cw.size() | 1086 size = self.cw.size() |
1032 self.setCentralWidget(self.cw) | 1087 self.setCentralWidget(self.cw) |
1074 @type bool | 1129 @type bool |
1075 """ | 1130 """ |
1076 pluginsRegister = [] # list of plug-ins contained in the repository | 1131 pluginsRegister = [] # list of plug-ins contained in the repository |
1077 | 1132 |
1078 def registerPlugin( | 1133 def registerPlugin( |
1079 name, short, description, url, author, version, filename, status # noqa: U100 | 1134 name, # noqa: U100 |
1135 short, # noqa: U100 | |
1136 description, # noqa: U100 | |
1137 url, | |
1138 author, # noqa: U100 | |
1139 version, # noqa: U100 | |
1140 filename, # noqa: U100 | |
1141 status, # noqa: U100 | |
1142 category, # noqa: U100 | |
1080 ): | 1143 ): |
1081 """ | 1144 """ |
1082 Method to register a plug-in's data. | 1145 Method to register a plug-in's data. |
1083 | 1146 |
1084 @param name data for the name field (string) | 1147 @param name data for the name field |
1085 @param short data for the short field (string) | 1148 @type str |
1086 @param description data for the description field (list of strings) | 1149 @param short data for the short field |
1087 @param url data for the url field (string) | 1150 @type str |
1088 @param author data for the author field (string) | 1151 @param description data for the description field |
1089 @param version data for the version field (string) | 1152 @type list of str |
1090 @param filename data for the filename field (string) | 1153 @param url data for the url field |
1091 @param status status of the plugin (string [stable, unstable, unknown]) | 1154 @type str |
1155 @param author data for the author field | |
1156 @type str | |
1157 @param version data for the version field | |
1158 @type str | |
1159 @param filename data for the filename field | |
1160 @type str | |
1161 @param status status of the plugin (one of stable, unstable, unknown) | |
1162 @type str | |
1163 @param category category designation of the plugin | |
1164 @type str | |
1092 """ | 1165 """ |
1093 pluginName = os.path.splitext(url.rsplit("/", 1)[1])[0] | 1166 pluginName = os.path.splitext(url.rsplit("/", 1)[1])[0] |
1094 if pluginName not in pluginsRegister: | 1167 if pluginName not in pluginsRegister: |
1095 pluginsRegister.append(pluginName) | 1168 pluginsRegister.append(pluginName) |
1096 | 1169 |