66 """ |
72 """ |
67 super(PluginRepositoryWidget, self).__init__(parent) |
73 super(PluginRepositoryWidget, self).__init__(parent) |
68 self.setupUi(self) |
74 self.setupUi(self) |
69 |
75 |
70 self.__updateButton = self.buttonBox.addButton( |
76 self.__updateButton = self.buttonBox.addButton( |
71 self.trUtf8("Update"), QDialogButtonBox.ActionRole) |
77 self.tr("Update"), QDialogButtonBox.ActionRole) |
72 self.__downloadButton = self.buttonBox.addButton( |
78 self.__downloadButton = self.buttonBox.addButton( |
73 self.trUtf8("Download"), QDialogButtonBox.ActionRole) |
79 self.tr("Download"), QDialogButtonBox.ActionRole) |
74 self.__downloadButton.setEnabled(False) |
80 self.__downloadButton.setEnabled(False) |
75 self.__downloadInstallButton = self.buttonBox.addButton( |
81 self.__downloadInstallButton = self.buttonBox.addButton( |
76 self.trUtf8("Download && Install"), |
82 self.tr("Download && Install"), |
77 QDialogButtonBox.ActionRole) |
83 QDialogButtonBox.ActionRole) |
78 self.__downloadInstallButton.setEnabled(False) |
84 self.__downloadInstallButton.setEnabled(False) |
79 self.__downloadCancelButton = self.buttonBox.addButton( |
85 self.__downloadCancelButton = self.buttonBox.addButton( |
80 self.trUtf8("Cancel"), QDialogButtonBox.ActionRole) |
86 self.tr("Cancel"), QDialogButtonBox.ActionRole) |
81 self.__installButton = \ |
87 self.__installButton = \ |
82 self.buttonBox.addButton(self.trUtf8("Close && Install"), |
88 self.buttonBox.addButton(self.tr("Close && Install"), |
83 QDialogButtonBox.ActionRole) |
89 QDialogButtonBox.ActionRole) |
84 self.__downloadCancelButton.setEnabled(False) |
90 self.__downloadCancelButton.setEnabled(False) |
85 self.__installButton.setEnabled(False) |
91 self.__installButton.setEnabled(False) |
86 |
92 |
87 self.repositoryUrlEdit.setText( |
93 self.repositoryUrlEdit.setText( |
88 Preferences.getUI("PluginRepositoryUrl5")) |
94 Preferences.getUI("PluginRepositoryUrl5")) |
89 |
95 |
90 self.repositoryList.headerItem().setText( |
96 self.repositoryList.headerItem().setText( |
91 self.repositoryList.columnCount(), "") |
97 self.repositoryList.columnCount(), "") |
92 self.repositoryList.header().setSortIndicator(0, Qt.AscendingOrder) |
98 self.repositoryList.header().setSortIndicator(0, Qt.AscendingOrder) |
|
99 |
|
100 self.__pluginContextMenu = QMenu(self) |
|
101 self.__hideAct = self.__pluginContextMenu.addAction( |
|
102 self.tr("Hide"), self.__hidePlugin) |
|
103 self.__hideSelectedAct = self.__pluginContextMenu.addAction( |
|
104 self.tr("Hide Selected"), self.__hideSelectedPlugins) |
|
105 self.__pluginContextMenu.addSeparator() |
|
106 self.__showAllAct = self.__pluginContextMenu.addAction( |
|
107 self.tr("Show All"), self.__showAllPlugins) |
|
108 self.__pluginContextMenu.addSeparator() |
|
109 self.__pluginContextMenu.addAction( |
|
110 self.tr("Cleanup Downloads"), self.__cleanupDownloads) |
93 |
111 |
94 self.pluginRepositoryFile = \ |
112 self.pluginRepositoryFile = \ |
95 os.path.join(Utilities.getConfigDir(), "PluginRepository") |
113 os.path.join(Utilities.getConfigDir(), "PluginRepository") |
96 |
114 |
97 self.__external = external |
115 self.__external = external |
325 url = Preferences.getUI("PluginRepositoryUrl5") |
364 url = Preferences.getUI("PluginRepositoryUrl5") |
326 if url != self.repositoryUrlEdit.text(): |
365 if url != self.repositoryUrlEdit.text(): |
327 self.repositoryUrlEdit.setText(url) |
366 self.repositoryUrlEdit.setText(url) |
328 E5MessageBox.warning( |
367 E5MessageBox.warning( |
329 self, |
368 self, |
330 self.trUtf8("Plugins Repository URL Changed"), |
369 self.tr("Plugins Repository URL Changed"), |
331 self.trUtf8( |
370 self.tr( |
332 """The URL of the Plugins Repository has""" |
371 """The URL of the Plugins Repository has""" |
333 """ changed. Select the "Update" button to get""" |
372 """ changed. Select the "Update" button to get""" |
334 """ the new repository file.""")) |
373 """ the new repository file.""")) |
335 else: |
374 else: |
336 E5MessageBox.critical( |
375 E5MessageBox.critical( |
337 self, |
376 self, |
338 self.trUtf8("Read plugins repository file"), |
377 self.tr("Read plugins repository file"), |
339 self.trUtf8("<p>The plugins repository file <b>{0}</b> " |
378 self.tr("<p>The plugins repository file <b>{0}</b> " |
340 "could not be read. Select Update</p>") |
379 "could not be read. Select Update</p>") |
341 .format(self.pluginRepositoryFile)) |
380 .format(self.pluginRepositoryFile)) |
342 else: |
381 else: |
343 self.__repositoryMissing = True |
382 self.__repositoryMissing = True |
344 QTreeWidgetItem( |
383 QTreeWidgetItem( |
345 self.repositoryList, |
384 self.repositoryList, |
346 ["", self.trUtf8( |
385 ["", self.tr( |
347 "No plugin repository file available.\nSelect Update.") |
386 "No plugin repository file available.\nSelect Update.") |
348 ]) |
387 ]) |
349 self.repositoryList.resizeColumnToContents(1) |
388 self.repositoryList.resizeColumnToContents(1) |
350 |
389 |
351 def __downloadFile(self, url, filename, doneMethod=None): |
390 def __downloadFile(self, url, filename, doneMethod=None): |
461 @param author data for the author field (string) |
500 @param author data for the author field (string) |
462 @param version data for the version field (string) |
501 @param version data for the version field (string) |
463 @param filename data for the filename field (string) |
502 @param filename data for the filename field (string) |
464 @param status status of the plugin (string [stable, unstable, unknown]) |
503 @param status status of the plugin (string [stable, unstable, unknown]) |
465 """ |
504 """ |
|
505 pluginName = filename.rsplit("-", 1)[0] |
|
506 if pluginName in self.__hiddenPlugins: |
|
507 return |
|
508 |
466 if status == "stable": |
509 if status == "stable": |
467 if self.__stableItem is None: |
510 if self.__stableItem is None: |
468 self.__stableItem = \ |
511 self.__stableItem = \ |
469 QTreeWidgetItem(self.repositoryList, |
512 QTreeWidgetItem(self.repositoryList, |
470 [self.trUtf8("Stable")]) |
513 [self.tr("Stable")]) |
471 self.__stableItem.setExpanded(True) |
514 self.__stableItem.setExpanded(True) |
472 parent = self.__stableItem |
515 parent = self.__stableItem |
473 elif status == "unstable": |
516 elif status == "unstable": |
474 if self.__unstableItem is None: |
517 if self.__unstableItem is None: |
475 self.__unstableItem = \ |
518 self.__unstableItem = \ |
476 QTreeWidgetItem(self.repositoryList, |
519 QTreeWidgetItem(self.repositoryList, |
477 [self.trUtf8("Unstable")]) |
520 [self.tr("Unstable")]) |
478 self.__unstableItem.setExpanded(True) |
521 self.__unstableItem.setExpanded(True) |
479 parent = self.__unstableItem |
522 parent = self.__unstableItem |
480 else: |
523 else: |
481 if self.__unknownItem is None: |
524 if self.__unknownItem is None: |
482 self.__unknownItem = \ |
525 self.__unknownItem = \ |
483 QTreeWidgetItem(self.repositoryList, |
526 QTreeWidgetItem(self.repositoryList, |
484 [self.trUtf8("Unknown")]) |
527 [self.tr("Unknown")]) |
485 self.__unknownItem.setExpanded(True) |
528 self.__unknownItem.setExpanded(True) |
486 parent = self.__unknownItem |
529 parent = self.__unknownItem |
487 itm = QTreeWidgetItem(parent, [name, version, short]) |
530 itm = QTreeWidgetItem(parent, [name, version, short]) |
488 |
531 |
489 itm.setData(0, urlRole, url) |
532 itm.setData(0, PluginRepositoryWidget.UrlRole, url) |
490 itm.setData(0, filenameRole, filename) |
533 itm.setData(0, PluginRepositoryWidget.FilenameRole, filename) |
491 itm.setData(0, authorRole, author) |
534 itm.setData(0, PluginRepositoryWidget.AuthorRole, author) |
492 itm.setData(0, descrRole, description) |
535 itm.setData(0, PluginRepositoryWidget.DescrRole, description) |
493 |
536 |
494 if self.__isUpToDate(filename, version): |
537 updateStatus = self.__updateStatus(filename, version) |
|
538 if updateStatus == PluginRepositoryWidget.PluginStatusUpToDate: |
495 itm.setIcon(1, UI.PixmapCache.getIcon("empty.png")) |
539 itm.setIcon(1, UI.PixmapCache.getIcon("empty.png")) |
496 else: |
540 itm.setToolTip(1, self.tr("up-to-date")) |
|
541 elif updateStatus == PluginRepositoryWidget.PluginStatusNew: |
497 itm.setIcon(1, UI.PixmapCache.getIcon("download.png")) |
542 itm.setIcon(1, UI.PixmapCache.getIcon("download.png")) |
498 |
543 itm.setToolTip(1, self.tr("new download available")) |
499 def __isUpToDate(self, filename, version): |
544 elif updateStatus == PluginRepositoryWidget.PluginStatusLocalUpdate: |
500 """ |
545 itm.setIcon(1, UI.PixmapCache.getIcon("updateLocal.png")) |
501 Private method to check, if the given archive is up-to-date. |
546 itm.setToolTip(1, self.tr("update installable")) |
|
547 elif updateStatus == PluginRepositoryWidget.PluginStatusRemoteUpdate: |
|
548 itm.setIcon(1, UI.PixmapCache.getIcon("updateRemote.png")) |
|
549 itm.setToolTip(1, self.tr("updated download available")) |
|
550 |
|
551 def __updateStatus(self, filename, version): |
|
552 """ |
|
553 Private method to check, if the given archive update status. |
502 |
554 |
503 @param filename data for the filename field (string) |
555 @param filename data for the filename field (string) |
504 @param version data for the version field (string) |
556 @param version data for the version field (string) |
505 @return flag indicating up-to-date (boolean) |
557 @return plug-in update status (integer, one of PluginStatusNew, |
|
558 PluginStatusUpToDate, PluginStatusLocalUpdate, |
|
559 PluginStatusRemoteUpdate) |
506 """ |
560 """ |
507 archive = os.path.join(Preferences.getPluginManager("DownloadPath"), |
561 archive = os.path.join(Preferences.getPluginManager("DownloadPath"), |
508 filename) |
562 filename) |
509 |
563 |
|
564 # check, if it is an update (i.e. we already have archives |
|
565 # with the same pattern) |
|
566 archivesPattern = archive.rsplit('-', 1)[0] + "-*.zip" |
|
567 if len(glob.glob(archivesPattern)) == 0: |
|
568 return PluginRepositoryWidget.PluginStatusNew |
|
569 |
510 # check, if the archive exists |
570 # check, if the archive exists |
511 if not os.path.exists(archive): |
571 if not os.path.exists(archive): |
512 return False |
572 return PluginRepositoryWidget.PluginStatusRemoteUpdate |
513 |
573 |
514 # check, if the archive is a valid zip file |
574 # check, if the archive is a valid zip file |
515 if not zipfile.is_zipfile(archive): |
575 if not zipfile.is_zipfile(archive): |
516 return False |
576 return PluginRepositoryWidget.PluginStatusRemoteUpdate |
517 |
577 |
518 zip = zipfile.ZipFile(archive, "r") |
578 zip = zipfile.ZipFile(archive, "r") |
519 try: |
579 try: |
520 aversion = zip.read("VERSION").decode("utf-8") |
580 aversion = zip.read("VERSION").decode("utf-8") |
521 except KeyError: |
581 except KeyError: |
522 aversion = "" |
582 aversion = "" |
523 zip.close() |
583 zip.close() |
524 |
584 |
525 return aversion == version |
585 if aversion == version: |
|
586 if not self.__external: |
|
587 # Check against installed/loaded plug-ins |
|
588 pluginManager = e5App().getObject("PluginManager") |
|
589 pluginName = filename.rsplit('-', 1)[0] |
|
590 pluginDetails = pluginManager.getPluginDetails(pluginName) |
|
591 if pluginDetails is None or pluginDetails["version"] < version: |
|
592 return PluginRepositoryWidget.PluginStatusLocalUpdate |
|
593 |
|
594 return PluginRepositoryWidget.PluginStatusUpToDate |
|
595 else: |
|
596 return PluginRepositoryWidget.PluginStatusRemoteUpdate |
526 |
597 |
527 def __sslErrors(self, reply, errors): |
598 def __sslErrors(self, reply, errors): |
528 """ |
599 """ |
529 Private slot to handle SSL errors. |
600 Private slot to handle SSL errors. |
530 |
601 |
550 edit. |
621 edit. |
551 |
622 |
552 @param checked state of the push button (boolean) |
623 @param checked state of the push button (boolean) |
553 """ |
624 """ |
554 self.repositoryUrlEdit.setReadOnly(not checked) |
625 self.repositoryUrlEdit.setReadOnly(not checked) |
|
626 |
|
627 def __closeAndInstall(self): |
|
628 """ |
|
629 Private method to close the dialog and invoke the install dialog. |
|
630 """ |
|
631 if not self.__pluginsDownloaded and self.__selectedItems(): |
|
632 for itm in self.__selectedItems(): |
|
633 filename = os.path.join( |
|
634 Preferences.getPluginManager("DownloadPath"), |
|
635 itm.data(0, PluginRepositoryWidget.FilenameRole)) |
|
636 self.__pluginsDownloaded.append(filename) |
|
637 self.closeAndInstall.emit() |
|
638 |
|
639 def __hidePlugin(self): |
|
640 """ |
|
641 Private slot to hide the current plug-in. |
|
642 """ |
|
643 itm = self.__selectedItems()[0] |
|
644 pluginName = (itm.data(0, PluginRepositoryWidget.FilenameRole) |
|
645 .rsplit("-", 1)[0]) |
|
646 self.__updateHiddenPluginsList([pluginName]) |
|
647 |
|
648 def __hideSelectedPlugins(self): |
|
649 """ |
|
650 Private slot to hide all selected plug-ins. |
|
651 """ |
|
652 hideList = [] |
|
653 for itm in self.__selectedItems(): |
|
654 pluginName = (itm.data(0, PluginRepositoryWidget.FilenameRole) |
|
655 .rsplit("-", 1)[0]) |
|
656 hideList.append(pluginName) |
|
657 self.__updateHiddenPluginsList(hideList) |
|
658 |
|
659 def __showAllPlugins(self): |
|
660 """ |
|
661 Private slot to show all plug-ins. |
|
662 """ |
|
663 self.__hiddenPlugins = [] |
|
664 self.__updateHiddenPluginsList([]) |
|
665 |
|
666 def __hasHiddenPlugins(self): |
|
667 """ |
|
668 Private method to check, if there are any hidden plug-ins. |
|
669 |
|
670 @return flag indicating the presence of hidden plug-ins (boolean) |
|
671 """ |
|
672 return bool(self.__hiddenPlugins) |
|
673 |
|
674 def __updateHiddenPluginsList(self, hideList): |
|
675 """ |
|
676 Private method to store the list of hidden plug-ins to the settings. |
|
677 |
|
678 @param hideList list of plug-ins to add to the list of hidden ones |
|
679 (list of string) |
|
680 """ |
|
681 if hideList: |
|
682 self.__hiddenPlugins.extend( |
|
683 [p for p in hideList if p not in self.__hiddenPlugins]) |
|
684 Preferences.setPluginManager("HiddenPlugins", self.__hiddenPlugins) |
|
685 self.__populateList() |
|
686 |
|
687 def __cleanupDownloads(self): |
|
688 """ |
|
689 Private slot to cleanup the plug-in downloads area. |
|
690 """ |
|
691 downloadPath = Preferences.getPluginManager("DownloadPath") |
|
692 downloads = {} # plug-in name as key, file name as value |
|
693 |
|
694 # step 1: extract plug-ins and downloaded files |
|
695 for pluginFile in os.listdir(downloadPath): |
|
696 if not os.path.isfile(os.path.join(downloadPath, pluginFile)): |
|
697 continue |
|
698 |
|
699 pluginName = pluginFile.rsplit("-", 1)[0] |
|
700 if pluginName not in downloads: |
|
701 downloads[pluginName] = [] |
|
702 downloads[pluginName].append(pluginFile) |
|
703 |
|
704 # step 2: delete old entries |
|
705 for pluginName in downloads: |
|
706 downloads[pluginName].sort() |
|
707 |
|
708 if pluginName in self.__hiddenPlugins and \ |
|
709 not Preferences.getPluginManager("KeepHidden"): |
|
710 removeFiles = downloads[pluginName] |
|
711 else: |
|
712 removeFiles = downloads[pluginName][ |
|
713 :-Preferences.getPluginManager("KeepGenerations")] |
|
714 for removeFile in removeFiles: |
|
715 try: |
|
716 os.remove(os.path.join(downloadPath, removeFile)) |
|
717 except (IOError, OSError) as err: |
|
718 E5MessageBox.critical( |
|
719 self, |
|
720 self.tr("Cleanup of Plugin Downloads"), |
|
721 self.tr("""<p>The plugin download <b>{0}</b> could""" |
|
722 """ not be deleted.</p><p>Reason: {1}</p>""") |
|
723 .format(removeFile, str(err))) |
555 |
724 |
556 |
725 |
557 class PluginRepositoryDialog(QDialog): |
726 class PluginRepositoryDialog(QDialog): |
558 """ |
727 """ |
559 Class for the dialog variant. |
728 Class for the dialog variant. |