10 from __future__ import unicode_literals # __IGNORE_WARNING__ |
10 from __future__ import unicode_literals # __IGNORE_WARNING__ |
11 |
11 |
12 import os |
12 import os |
13 import sys |
13 import sys |
14 import imp |
14 import imp |
15 |
15 import zipfile |
16 from PyQt4.QtCore import pyqtSignal, QObject |
16 |
|
17 from PyQt4.QtCore import pyqtSignal, QObject, QDate, QFile, QFileInfo, QUrl, \ |
|
18 QIODevice |
17 from PyQt4.QtGui import QPixmap |
19 from PyQt4.QtGui import QPixmap |
|
20 from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest, \ |
|
21 QNetworkReply |
18 |
22 |
19 from E5Gui import E5MessageBox |
23 from E5Gui import E5MessageBox |
|
24 |
|
25 from E5Network.E5NetworkProxyFactory import proxyAuthenticationRequired |
|
26 try: |
|
27 from E5Network.E5SslErrorHandler import E5SslErrorHandler |
|
28 SSL_AVAILABLE = True |
|
29 except ImportError: |
|
30 SSL_AVAILABLE = False |
20 |
31 |
21 from .PluginExceptions import PluginPathError, PluginModulesError, \ |
32 from .PluginExceptions import PluginPathError, PluginModulesError, \ |
22 PluginLoadError, PluginActivationError, PluginModuleFormatError, \ |
33 PluginLoadError, PluginActivationError, PluginModuleFormatError, \ |
23 PluginClassFormatError |
34 PluginClassFormatError |
24 |
35 |
660 (boolean), short description (string), error flag (boolean) |
683 (boolean), short description (string), error flag (boolean) |
661 """ |
684 """ |
662 infos = [] |
685 infos = [] |
663 |
686 |
664 for name in list(self.__activeModules.keys()): |
687 for name in list(self.__activeModules.keys()): |
665 pname, shortDesc, error, version = \ |
688 pname, shortDesc, error, version = \ |
666 self.__getShortInfo(self.__activeModules[name]) |
689 self.__getShortInfo(self.__activeModules[name]) |
667 infos.append((name, pname, version, True, True, shortDesc, error)) |
690 infos.append((name, pname, version, True, True, shortDesc, error)) |
668 for name in list(self.__inactiveModules.keys()): |
691 for name in list(self.__inactiveModules.keys()): |
669 pname, shortDesc, error, version = \ |
692 pname, shortDesc, error, version = \ |
670 self.__getShortInfo(self.__inactiveModules[name]) |
693 self.__getShortInfo(self.__inactiveModules[name]) |
671 infos.append( |
694 infos.append( |
672 (name, pname, version, True, False, shortDesc, error)) |
695 (name, pname, version, True, False, shortDesc, error)) |
673 for name in list(self.__onDemandActiveModules.keys()): |
696 for name in list(self.__onDemandActiveModules.keys()): |
674 pname, shortDesc, error, version = \ |
697 pname, shortDesc, error, version = \ |
675 self.__getShortInfo(self.__onDemandActiveModules[name]) |
698 self.__getShortInfo(self.__onDemandActiveModules[name]) |
676 infos.append( |
699 infos.append( |
677 (name, pname, version, False, True, shortDesc, error)) |
700 (name, pname, version, False, True, shortDesc, error)) |
678 for name in list(self.__onDemandInactiveModules.keys()): |
701 for name in list(self.__onDemandInactiveModules.keys()): |
679 pname, shortDesc, error, version = \ |
702 pname, shortDesc, error, version = \ |
680 self.__getShortInfo(self.__onDemandInactiveModules[name]) |
703 self.__getShortInfo(self.__onDemandInactiveModules[name]) |
681 infos.append( |
704 infos.append( |
682 (name, pname, version, False, False, shortDesc, error)) |
705 (name, pname, version, False, False, shortDesc, error)) |
683 for name in list(self.__failedModules.keys()): |
706 for name in list(self.__failedModules.keys()): |
684 pname, shortDesc, error, version = \ |
707 pname, shortDesc, error, version = \ |
685 self.__getShortInfo(self.__failedModules[name]) |
708 self.__getShortInfo(self.__failedModules[name]) |
686 infos.append( |
709 infos.append( |
687 (name, pname, version, False, False, shortDesc, error)) |
710 (name, pname, version, False, False, shortDesc, error)) |
688 return infos |
711 return infos |
689 |
712 |
690 def __getShortInfo(self, module): |
713 def __getShortInfo(self, module): |
691 """ |
714 """ |
692 Private method to extract the short info from a module. |
715 Private method to extract the short info from a module. |
1015 def preferencesChanged(self): |
1038 def preferencesChanged(self): |
1016 """ |
1039 """ |
1017 Public slot to react to changes in configuration. |
1040 Public slot to react to changes in configuration. |
1018 """ |
1041 """ |
1019 self.__checkPluginsDownloadDirectory() |
1042 self.__checkPluginsDownloadDirectory() |
|
1043 |
|
1044 ######################################################################## |
|
1045 ## Methods for automatic plug-in update check below |
|
1046 ######################################################################## |
|
1047 |
|
1048 def checkPluginUpdatesAvailable(self): |
|
1049 """ |
|
1050 Public method to check the availability of updates of plug-ins. |
|
1051 """ |
|
1052 period = Preferences.getPluginManager("UpdatesCheckInterval") |
|
1053 if period == 0: |
|
1054 return |
|
1055 elif period in [2, 3, 4]: |
|
1056 lastModified = QFileInfo(self.pluginRepositoryFile).lastModified() |
|
1057 if lastModified.isValid() and lastModified.date().isValid(): |
|
1058 lastModifiedDate = lastModified.date() |
|
1059 now = QDate.currentDate() |
|
1060 if period == 2 and lastModifiedDate.day() == now.day(): |
|
1061 # daily |
|
1062 return |
|
1063 elif period == 3 and lastModifiedDate.daysTo(now) < 7: |
|
1064 # weekly |
|
1065 return |
|
1066 elif period == 4 and lastModifiedDate.month() == now.month(): |
|
1067 # monthly |
|
1068 return |
|
1069 |
|
1070 self.__updateAvailable = False |
|
1071 |
|
1072 request = QNetworkRequest( |
|
1073 QUrl(Preferences.getUI("PluginRepositoryUrl5"))) |
|
1074 request.setAttribute(QNetworkRequest.CacheLoadControlAttribute, |
|
1075 QNetworkRequest.AlwaysNetwork) |
|
1076 reply = self.__networkManager.get(request) |
|
1077 reply.finished[()].connect(self.__downloadRepositoryFileDone) |
|
1078 self.__replies.append(reply) |
|
1079 |
|
1080 def __downloadRepositoryFileDone(self): |
|
1081 """ |
|
1082 Private method called after the repository file was downloaded. |
|
1083 """ |
|
1084 reply = self.sender() |
|
1085 if reply in self.__replies: |
|
1086 self.__replies.remove(reply) |
|
1087 if reply.error() != QNetworkReply.NoError: |
|
1088 E5MessageBox.warning( |
|
1089 None, |
|
1090 self.trUtf8("Error downloading file"), |
|
1091 self.trUtf8( |
|
1092 """<p>Could not download the requested file""" |
|
1093 """ from {0}.</p><p>Error: {1}</p>""" |
|
1094 ).format(Preferences.getUI("PluginRepositoryUrl5"), |
|
1095 reply.errorString()) |
|
1096 ) |
|
1097 return |
|
1098 |
|
1099 ioDevice = QFile(self.pluginRepositoryFile + ".tmp") |
|
1100 ioDevice.open(QIODevice.WriteOnly) |
|
1101 ioDevice.write(reply.readAll()) |
|
1102 ioDevice.close() |
|
1103 if QFile.exists(self.pluginRepositoryFile): |
|
1104 QFile.remove(self.pluginRepositoryFile) |
|
1105 ioDevice.rename(self.pluginRepositoryFile) |
|
1106 |
|
1107 if os.path.exists(self.pluginRepositoryFile): |
|
1108 f = QFile(self.pluginRepositoryFile) |
|
1109 if f.open(QIODevice.ReadOnly): |
|
1110 # save current URL |
|
1111 url = Preferences.getUI("PluginRepositoryUrl5") |
|
1112 |
|
1113 # read the repository file |
|
1114 from E5XML.PluginRepositoryReader import PluginRepositoryReader |
|
1115 reader = PluginRepositoryReader(f, self.checkPluginEntry) |
|
1116 reader.readXML() |
|
1117 if url != Preferences.getUI("PluginRepositoryUrl5"): |
|
1118 # redo if it is a redirect |
|
1119 self.checkPluginUpdatesAvailable() |
|
1120 return |
|
1121 |
|
1122 if self.__updateAvailable: |
|
1123 res = E5MessageBox.information( |
|
1124 None, |
|
1125 self.trUtf8("New plugin versions available"), |
|
1126 self.trUtf8("<p>There are new plug-ins or plug-in" |
|
1127 " updates available. Use the plug-in" |
|
1128 " repository dialog to get them.</p>"), |
|
1129 E5MessageBox.StandardButtons( |
|
1130 E5MessageBox.Ignore | \ |
|
1131 E5MessageBox.Open), |
|
1132 E5MessageBox.Open) |
|
1133 if res == E5MessageBox.Open: |
|
1134 self.__ui.showPluginsAvailable() |
|
1135 |
|
1136 def checkPluginEntry(self, name, short, description, url, author, version, |
|
1137 filename, status): |
|
1138 """ |
|
1139 Public method to check a plug-in's data for an update. |
|
1140 |
|
1141 @param name data for the name field (string) |
|
1142 @param short data for the short field (string) |
|
1143 @param description data for the description field (list of strings) |
|
1144 @param url data for the url field (string) |
|
1145 @param author data for the author field (string) |
|
1146 @param version data for the version field (string) |
|
1147 @param filename data for the filename field (string) |
|
1148 @param status status of the plugin (string [stable, unstable, unknown]) |
|
1149 """ |
|
1150 archive = os.path.join(Preferences.getPluginManager("DownloadPath"), |
|
1151 filename) |
|
1152 |
|
1153 # Check against installed/loaded plug-ins |
|
1154 pluginName = os.path.splitext(url.rsplit("/", 1)[1])[0] |
|
1155 pluginDetails = self.getPluginDetails(pluginName) |
|
1156 if pluginDetails is None: |
|
1157 if not Preferences.getPluginManager("CheckInstalledOnly"): |
|
1158 self.__updateAvailable = True |
|
1159 return |
|
1160 |
|
1161 if pluginDetails["version"] < version: |
|
1162 self.__updateAvailable = True |
|
1163 return |
|
1164 |
|
1165 if not Preferences.getPluginManager("CheckInstalledOnly"): |
|
1166 # Check against downloaded plugin archives |
|
1167 # 1. Check, if the archive file exists |
|
1168 if not os.path.exists(archive): |
|
1169 self.__updateAvailable = True |
|
1170 return |
|
1171 |
|
1172 # 2. Check, if the archive is a valid zip file |
|
1173 if not zipfile.is_zipfile(archive): |
|
1174 self.__updateAvailable = True |
|
1175 return |
|
1176 |
|
1177 # 3. Check the version of the archive file |
|
1178 zip = zipfile.ZipFile(archive, "r") |
|
1179 try: |
|
1180 aversion = zip.read("VERSION").decode("utf-8") |
|
1181 except KeyError: |
|
1182 aversion = "" |
|
1183 zip.close() |
|
1184 |
|
1185 if aversion != version: |
|
1186 self.__updateAvailable = True |
|
1187 |
|
1188 def __sslErrors(self, reply, errors): |
|
1189 """ |
|
1190 Private slot to handle SSL errors. |
|
1191 |
|
1192 @param reply reference to the reply object (QNetworkReply) |
|
1193 @param errors list of SSL errors (list of QSslError) |
|
1194 """ |
|
1195 ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0] |
|
1196 if ignored == E5SslErrorHandler.NotIgnored: |
|
1197 self.__downloadCancelled = True |