PluginManager/PluginManager.py

branch
Py2 comp.
changeset 3142
55030c09e142
parent 3060
5883ce99ee12
parent 3116
ee0a183cec81
child 3145
a9de05d4a22f
--- a/PluginManager/PluginManager.py	Fri Dec 13 22:45:47 2013 +0100
+++ b/PluginManager/PluginManager.py	Fri Dec 13 23:39:14 2013 +0100
@@ -12,12 +12,23 @@
 import os
 import sys
 import imp
+import zipfile
 
-from PyQt4.QtCore import pyqtSignal, QObject
+from PyQt4.QtCore import pyqtSignal, QObject, QDate, QFile, QFileInfo, QUrl, \
+    QIODevice
 from PyQt4.QtGui import QPixmap
+from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest, \
+    QNetworkReply
 
 from E5Gui import E5MessageBox
 
+from E5Network.E5NetworkProxyFactory import proxyAuthenticationRequired
+try:
+    from E5Network.E5SslErrorHandler import E5SslErrorHandler
+    SSL_AVAILABLE = True
+except ImportError:
+    SSL_AVAILABLE = False
+
 from .PluginExceptions import PluginPathError, PluginModulesError, \
     PluginLoadError, PluginActivationError, PluginModuleFormatError, \
     PluginClassFormatError
@@ -119,6 +130,18 @@
             self.__loadPlugins()
         
         self.__checkPluginsDownloadDirectory()
+        
+        self.pluginRepositoryFile = \
+            os.path.join(Utilities.getConfigDir(), "PluginRepository")
+        
+        # attributes for the network objects
+        self.__networkManager = QNetworkAccessManager(self)
+        self.__networkManager.proxyAuthenticationRequired.connect(
+            proxyAuthenticationRequired)
+        if SSL_AVAILABLE:
+            self.__sslErrorHandler = E5SslErrorHandler(self)
+            self.__networkManager.sslErrors.connect(self.__sslErrors)
+        self.__replies = []
     
     def finalizeSetup(self):
         """
@@ -170,7 +193,7 @@
             fname = os.path.join(self.pluginDirs["user"], "__init__.py")
             if not os.path.exists(fname):
                 if not os.path.exists(self.pluginDirs["user"]):
-                    os.mkdir(self.pluginDirs["user"],  0o755)
+                    os.mkdir(self.pluginDirs["user"], 0o755)
                 try:
                     f = open(fname, "w")
                     f.close()
@@ -343,7 +366,7 @@
             module.error = self.trUtf8(
                 "Module failed to load. Error: {0}").format(str(err))
             self.__failedModules[name] = module
-            print("Error loading plugin module:",  name)
+            print("Error loading plugin module:", name)
             print(str(err))
     
     def unloadPlugin(self, name):
@@ -662,29 +685,29 @@
         infos = []
         
         for name in list(self.__activeModules.keys()):
-            pname,  shortDesc, error, version = \
+            pname, shortDesc, error, version = \
                 self.__getShortInfo(self.__activeModules[name])
-            infos.append((name,  pname, version, True, True, shortDesc, error))
+            infos.append((name, pname, version, True, True, shortDesc, error))
         for name in list(self.__inactiveModules.keys()):
-            pname,  shortDesc, error, version = \
+            pname, shortDesc, error, version = \
                 self.__getShortInfo(self.__inactiveModules[name])
             infos.append(
-                (name,  pname, version, True, False, shortDesc, error))
+                (name, pname, version, True, False, shortDesc, error))
         for name in list(self.__onDemandActiveModules.keys()):
-            pname,  shortDesc, error, version = \
+            pname, shortDesc, error, version = \
                 self.__getShortInfo(self.__onDemandActiveModules[name])
             infos.append(
-                (name,  pname, version, False, True, shortDesc, error))
+                (name, pname, version, False, True, shortDesc, error))
         for name in list(self.__onDemandInactiveModules.keys()):
-            pname,  shortDesc, error, version = \
+            pname, shortDesc, error, version = \
                 self.__getShortInfo(self.__onDemandInactiveModules[name])
             infos.append(
-                (name,  pname, version, False, False, shortDesc, error))
+                (name, pname, version, False, False, shortDesc, error))
         for name in list(self.__failedModules.keys()):
-            pname,  shortDesc, error, version = \
+            pname, shortDesc, error, version = \
                 self.__getShortInfo(self.__failedModules[name])
             infos.append(
-                (name,  pname, version, False, False, shortDesc, error))
+                (name, pname, version, False, False, shortDesc, error))
         return infos
     
     def __getShortInfo(self, module):
@@ -1017,3 +1040,158 @@
         Public slot to react to changes in configuration.
         """
         self.__checkPluginsDownloadDirectory()
+    
+    ########################################################################
+    ## Methods for automatic plug-in update check below
+    ########################################################################
+    
+    def checkPluginUpdatesAvailable(self):
+        """
+        Public method to check the availability of updates of plug-ins.
+        """
+        period = Preferences.getPluginManager("UpdatesCheckInterval")
+        if period == 0:
+            return
+        elif period in [2, 3, 4]:
+            lastModified = QFileInfo(self.pluginRepositoryFile).lastModified()
+            if lastModified.isValid() and lastModified.date().isValid():
+                lastModifiedDate = lastModified.date()
+                now = QDate.currentDate()
+                if period == 2 and lastModifiedDate.day() == now.day():
+                    # daily
+                    return
+                elif period == 3 and lastModifiedDate.daysTo(now) < 7:
+                    # weekly
+                    return
+                elif period == 4 and lastModifiedDate.month() == now.month():
+                    # monthly
+                    return
+        
+        self.__updateAvailable = False
+        
+        request = QNetworkRequest(
+            QUrl(Preferences.getUI("PluginRepositoryUrl5")))
+        request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
+                             QNetworkRequest.AlwaysNetwork)
+        reply = self.__networkManager.get(request)
+        reply.finished[()].connect(self.__downloadRepositoryFileDone)
+        self.__replies.append(reply)
+    
+    def __downloadRepositoryFileDone(self):
+        """
+        Private method called after the repository file was downloaded.
+        """
+        reply = self.sender()
+        if reply in self.__replies:
+            self.__replies.remove(reply)
+        if reply.error() != QNetworkReply.NoError:
+            E5MessageBox.warning(
+                None,
+                self.trUtf8("Error downloading file"),
+                self.trUtf8(
+                    """<p>Could not download the requested file"""
+                    """ from {0}.</p><p>Error: {1}</p>"""
+                ).format(Preferences.getUI("PluginRepositoryUrl5"),
+                         reply.errorString())
+            )
+            return
+        
+        ioDevice = QFile(self.pluginRepositoryFile + ".tmp")
+        ioDevice.open(QIODevice.WriteOnly)
+        ioDevice.write(reply.readAll())
+        ioDevice.close()
+        if QFile.exists(self.pluginRepositoryFile):
+            QFile.remove(self.pluginRepositoryFile)
+        ioDevice.rename(self.pluginRepositoryFile)
+        
+        if os.path.exists(self.pluginRepositoryFile):
+            f = QFile(self.pluginRepositoryFile)
+            if f.open(QIODevice.ReadOnly):
+                # save current URL
+                url = Preferences.getUI("PluginRepositoryUrl5")
+                
+                # read the repository file
+                from E5XML.PluginRepositoryReader import PluginRepositoryReader
+                reader = PluginRepositoryReader(f, self.checkPluginEntry)
+                reader.readXML()
+                if url != Preferences.getUI("PluginRepositoryUrl5"):
+                    # redo if it is a redirect
+                    self.checkPluginUpdatesAvailable()
+                    return
+                
+                if self.__updateAvailable:
+                    res = E5MessageBox.information(
+                        None,
+                        self.trUtf8("New plugin versions available"),
+                        self.trUtf8("<p>There are new plug-ins or plug-in"
+                                    " updates available. Use the plug-in"
+                                    " repository dialog to get them.</p>"),
+                        E5MessageBox.StandardButtons(
+                            E5MessageBox.Ignore | \
+                            E5MessageBox.Open),
+                        E5MessageBox.Open)
+                    if res == E5MessageBox.Open:
+                        self.__ui.showPluginsAvailable()
+    
+    def checkPluginEntry(self, name, short, description, url, author, version,
+                         filename, status):
+        """
+        Public method to check a plug-in's data for an update.
+        
+        @param name data for the name field (string)
+        @param short data for the short field (string)
+        @param description data for the description field (list of strings)
+        @param url data for the url field (string)
+        @param author data for the author field (string)
+        @param version data for the version field (string)
+        @param filename data for the filename field (string)
+        @param status status of the plugin (string [stable, unstable, unknown])
+        """
+        archive = os.path.join(Preferences.getPluginManager("DownloadPath"),
+                               filename)
+        
+        # Check against installed/loaded plug-ins
+        pluginName = os.path.splitext(url.rsplit("/", 1)[1])[0]
+        pluginDetails = self.getPluginDetails(pluginName)
+        if pluginDetails is None:
+            if not Preferences.getPluginManager("CheckInstalledOnly"):
+                self.__updateAvailable = True
+            return
+        
+        if pluginDetails["version"] < version:
+            self.__updateAvailable = True
+            return
+        
+        if not Preferences.getPluginManager("CheckInstalledOnly"):
+            # Check against downloaded plugin archives
+            # 1. Check, if the archive file exists
+            if not os.path.exists(archive):
+                self.__updateAvailable = True
+                return
+            
+            # 2. Check, if the archive is a valid zip file
+            if not zipfile.is_zipfile(archive):
+                self.__updateAvailable = True
+                return
+            
+            # 3. Check the version of the archive file
+            zip = zipfile.ZipFile(archive, "r")
+            try:
+                aversion = zip.read("VERSION").decode("utf-8")
+            except KeyError:
+                aversion = ""
+            zip.close()
+            
+            if aversion != version:
+                self.__updateAvailable = True
+    
+    def __sslErrors(self, reply, errors):
+        """
+        Private slot to handle SSL errors.
+        
+        @param reply reference to the reply object (QNetworkReply)
+        @param errors list of SSL errors (list of QSslError)
+        """
+        ignored = self.__sslErrorHandler.sslErrorsReply(reply, errors)[0]
+        if ignored == E5SslErrorHandler.NotIgnored:
+            self.__downloadCancelled = True

eric ide

mercurial