QGIS/python/pyplugin_installer/installer_data.py

803 lines
35 KiB
Python

# -*- coding:utf-8 -*-
"""
/***************************************************************************
Plugin Installer module
-------------------
Date : May 2013
Copyright : (C) 2013 by Borys Jurgiel
Email : info at borysjurgiel dot pl
This module is based on former plugin_installer plugin:
Copyright (C) 2007-2008 Matthew Perry
Copyright (C) 2008-2013 Borys Jurgiel
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
from PyQt4.QtCore import *
from PyQt4.QtXml import QDomDocument
from PyQt4.QtNetwork import *
import sys
import os
import codecs
import ConfigParser
import qgis.utils
from qgis.core import *
from qgis.utils import iface, plugin_paths
from version_compare import compareVersions, normalizeVersion, isCompatible
"""
Data structure:
mRepositories = dict of dicts: {repoName : {"url" unicode,
"enabled" bool,
"valid" bool,
"Relay" Relay, # Relay object for transmitting signals from QPHttp with adding the repoName information
"Request" QNetworkRequest,
"xmlData" QNetworkReply,
"state" int, (0 - disabled, 1-loading, 2-loaded ok, 3-error (to be retried), 4-rejected)
"error" unicode}}
mPlugins = dict of dicts {id : {
"id" unicode # module name
"name" unicode, # human readable plugin name
"description" unicode, # short description of the plugin purpose only
"about" unicode, # longer description: how does it work, where does it install, how to run it?
"category" unicode, # will be removed?
"tags" unicode, # comma separated, spaces allowed
"changelog" unicode, # may be multiline
"author_name" unicode, # author name
"author_email" unicode, # author email
"homepage" unicode, # url to the plugin homepage
"tracker" unicode, # url to a tracker site
"code_repository" unicode, # url to the source code repository
"version_installed" unicode, # installed instance version
"library" unicode, # absolute path to the installed library / Python module
"icon" unicode, # path to the first:(INSTALLED | AVAILABLE) icon
"pythonic" const bool=True # True if Python plugin
"readonly" boolean, # True if core plugin
"installed" boolean, # True if installed
"available" boolean, # True if available in repositories
"status" unicode, # ( not installed | new ) | ( installed | upgradeable | orphan | newer )
"error" unicode, # NULL | broken | incompatible | dependent
"error_details" unicode, # error description
"experimental" boolean, # true if experimental, false if stable
"version_available" unicode, # available version
"zip_repository" unicode, # the remote repository id
"download_url" unicode, # url for downloading the plugin
"filename" unicode, # the zip file name to be unzipped after downloaded
"downloads" unicode, # number of dowloads
"average_vote" unicode, # average vote
"rating_votes" unicode, # number of votes
}}
"""
translatableAttributes = ["name", "description", "about", "tags"]
reposGroup = "/Qgis/plugin-repos"
settingsGroup = "/Qgis/plugin-installer"
seenPluginGroup = "/Qgis/plugin-seen"
# Repositories: (name, url, possible depreciated url)
officialRepo = ( QCoreApplication.translate("QgsPluginInstaller", "QGIS Official Plugin Repository"), "http://plugins.qgis.org/plugins/plugins.xml","http://plugins.qgis.org/plugins")
depreciatedRepos = [
("Old QGIS Official Repository", "http://pyqgis.org/repo/official"),
("Old QGIS Contributed Repository","http://pyqgis.org/repo/contributed"),
("Aaron Racicot's Repository", "http://qgisplugins.z-pulley.com"),
("Barry Rowlingson's Repository", "http://www.maths.lancs.ac.uk/~rowlings/Qgis/Plugins/plugins.xml"),
("Bob Bruce's Repository", "http://www.mappinggeek.ca/QGISPythonPlugins/Bobs-QGIS-plugins.xml"),
("Borys Jurgiel's Repository", "http://bwj.aster.net.pl/qgis/plugins.xml"),
("Carson Farmer's Repository", "http://www.ftools.ca/cfarmerQgisRepo.xml"),
("CatAIS Repository", "http://www.catais.org/qgis/plugins.xml"),
("Faunalia Repository", "http://www.faunalia.it/qgis/plugins.xml"),
("GIS-Lab Repository", "http://gis-lab.info/programs/qgis/qgis-repo.xml"),
("Kappasys Repository", "http://www.kappasys.org/qgis/plugins.xml"),
("Martin Dobias' Sandbox", "http://mapserver.sk/~wonder/qgis/plugins-sandbox.xml"),
("Marco Hugentobler's Repository", "http://karlinapp.ethz.ch/python_plugins/python_plugins.xml"),
("Sourcepole Repository", "http://build.sourcepole.ch/qgis/plugins.xml"),
("Volkan Kepoglu's Repository", "http://ggit.metu.edu.tr/~volkan/plugins.xml")
]
# --- common functions ------------------------------------------------------------------- #
def removeDir(path):
result = ""
if not QFile(path).exists():
result = QCoreApplication.translate("QgsPluginInstaller","Nothing to remove! Plugin directory doesn't exist:")+"\n"+path
elif QFile(path).remove(): # if it is only link, just remove it without resolving.
pass
else:
fltr = QDir.Dirs | QDir.Files | QDir.Hidden
iterator = QDirIterator(path, fltr, QDirIterator.Subdirectories)
while iterator.hasNext():
item = iterator.next()
if QFile(item).remove():
pass
fltr = QDir.Dirs | QDir.Hidden
iterator = QDirIterator(path, fltr, QDirIterator.Subdirectories)
while iterator.hasNext():
item = iterator.next()
if QDir().rmpath(item):
pass
if QFile(path).exists():
result = QCoreApplication.translate("QgsPluginInstaller","Failed to remove the directory:")+"\n"+path+"\n"+QCoreApplication.translate("QgsPluginInstaller","Check permissions or remove it manually")
# restore plugin directory if removed by QDir().rmpath()
pluginDir = QFileInfo(QgsApplication.qgisUserDbFilePath()).path() + "/python/plugins"
if not QDir(pluginDir).exists():
QDir().mkpath(pluginDir)
return result
# --- /common functions ------------------------------------------------------------------ #
# --- class Relay ----------------------------------------------------------------------- #
class Relay(QObject):
""" Relay object for transmitting signals from QPHttp with adding the repoName information """
# ----------------------------------------- #
anythingChanged = pyqtSignal( unicode, int, int )
def __init__(self, key):
QObject.__init__(self)
self.key = key
def stateChanged(self, state):
self.anythingChanged.emit( self.key, state, 0 )
# ----------------------------------------- #
def dataReadProgress(self, done, total):
state = 4
if total > 0:
progress = int(float(done)/float(total)*100)
else:
progress = 0
self.anythingChanged.emit( self.key, state, progress )
# --- /class Relay ---------------------------------------------------------------------- #
# --- class Repositories ----------------------------------------------------------------- #
class Repositories(QObject):
""" A dict-like class for handling repositories data """
# ----------------------------------------- #
anythingChanged = pyqtSignal( unicode, int, int )
repositoryFetched = pyqtSignal( unicode )
checkingDone = pyqtSignal()
def __init__(self):
QObject.__init__(self)
self.mRepositories = {}
self.httpId = {} # {httpId : repoName}
self.mInspectionFilter = None
# ----------------------------------------- #
def all(self):
""" return dict of all repositories """
return self.mRepositories
# ----------------------------------------- #
def allEnabled(self):
""" return dict of all enabled and valid repositories """
if self.mInspectionFilter:
return { self.mInspectionFilter: self.mRepositories[self.mInspectionFilter] }
repos = {}
for i in self.mRepositories:
if self.mRepositories[i]["enabled"] and self.mRepositories[i]["valid"]:
repos[i] = self.mRepositories[i]
return repos
# ----------------------------------------- #
def allUnavailable(self):
""" return dict of all unavailable repositories """
repos = {}
if self.mInspectionFilter:
# return the inspected repo if unavailable, otherwise empty dict
if self.mRepositories[self.mInspectionFilter]["state"] == 3:
repos [self.mInspectionFilter] = self.mRepositories[self.mInspectionFilter]
return repos
for i in self.mRepositories:
if self.mRepositories[i]["enabled"] and self.mRepositories[i]["valid"] and self.mRepositories[i]["state"] == 3:
repos[i] = self.mRepositories[i]
return repos
# ----------------------------------------- #
def urlParams(self):
""" return GET parameters to be added to every request """
v=str(QGis.QGIS_VERSION_INT)
return "?qgis=%d.%d" % ( int(v[0]), int(v[1:3]) )
# ----------------------------------------- #
def setRepositoryData(self, reposName, key, value):
""" write data to the mRepositories dict """
self.mRepositories[reposName][key] = value
# ----------------------------------------- #
def remove(self, reposName):
""" remove given item from the mRepositories dict """
del self.mRepositories[reposName]
# ----------------------------------------- #
def rename(self, oldName, newName):
""" rename repository key """
if oldName == newName:
return
self.mRepositories[newName] = self.mRepositories[oldName]
del self.mRepositories[oldName]
# ----------------------------------------- #
def checkingOnStart(self):
""" return true if checking for news and updates is enabled """
settings = QSettings()
return settings.value(settingsGroup+"/checkOnStart", False, type=bool)
# ----------------------------------------- #
def setCheckingOnStart(self, state):
""" set state of checking for news and updates """
settings = QSettings()
settings.setValue(settingsGroup+"/checkOnStart", state)
# ----------------------------------------- #
def checkingOnStartInterval(self):
""" return checking for news and updates interval """
settings = QSettings()
try:
# QSettings may contain non-int value...
i = settings.value(settingsGroup+"/checkOnStartInterval", 1, type=int)
except:
# fallback do 1 day by default
i = 1
if i < 0: i = 1
# allowed values: 0,1,3,7,14,30 days
interval = 0
for j in [1,3,7,14,30]:
if i >= j:
interval = j
return interval
# ----------------------------------------- #
def setCheckingOnStartInterval(self, interval):
""" set checking for news and updates interval """
settings = QSettings()
settings.setValue(settingsGroup+"/checkOnStartInterval", interval)
# ----------------------------------------- #
def saveCheckingOnStartLastDate(self):
""" set today's date as the day of last checking """
settings = QSettings()
settings.setValue(settingsGroup+"/checkOnStartLastDate", QDate.currentDate())
# ----------------------------------------- #
def timeForChecking(self):
""" determine whether it's the time for checking for news and updates now """
if self.checkingOnStartInterval() == 0:
return True
settings = QSettings()
try:
# QSettings may contain ivalid value...
interval = settings.value(settingsGroup+"/checkOnStartLastDate",type=QDate).daysTo(QDate.currentDate())
except:
interval = 0
if interval >= self.checkingOnStartInterval():
return True
else:
return False
# ----------------------------------------- #
def load(self):
""" populate the mRepositories dict"""
self.mRepositories = {}
settings = QSettings()
settings.beginGroup(reposGroup)
# first, update repositories in QSettings if needed
officialRepoPresent = False
for key in settings.childGroups():
url = settings.value(key+"/url", "", type=unicode)
if url == officialRepo[1]:
officialRepoPresent = True
if url == officialRepo[2]:
settings.setValue(key+"/url", officialRepo[1]) # correct a depreciated url
officialRepoPresent = True
if not officialRepoPresent:
settings.setValue(officialRepo[0]+"/url", officialRepo[1])
for key in settings.childGroups():
self.mRepositories[key] = {}
self.mRepositories[key]["url"] = settings.value(key+"/url", "", type=unicode)
self.mRepositories[key]["enabled"] = settings.value(key+"/enabled", True, type=bool)
self.mRepositories[key]["valid"] = settings.value(key+"/valid", True, type=bool)
self.mRepositories[key]["Relay"] = Relay(key)
self.mRepositories[key]["xmlData"] = None
self.mRepositories[key]["state"] = 0
self.mRepositories[key]["error"] = ""
settings.endGroup()
# ----------------------------------------- #
def requestFetching(self,key):
""" start fetching the repository given by key """
self.mRepositories[key]["state"] = 1
url = QUrl(self.mRepositories[key]["url"] + self.urlParams() )
#v=str(QGis.QGIS_VERSION_INT)
#url.addQueryItem('qgis', '.'.join([str(int(s)) for s in [v[0], v[1:3]]]) ) # don't include the bugfix version!
self.mRepositories[key]["QRequest"] = QNetworkRequest(url)
self.mRepositories[key]["QRequest"].setAttribute( QNetworkRequest.User, key)
self.mRepositories[key]["xmlData"] = QgsNetworkAccessManager.instance().get( self.mRepositories[key]["QRequest"] )
self.mRepositories[key]["xmlData"].setProperty( 'reposName', key)
self.mRepositories[key]["xmlData"].downloadProgress.connect( self.mRepositories[key]["Relay"].dataReadProgress )
self.mRepositories[key]["xmlData"].finished.connect( self.xmlDownloaded )
# ----------------------------------------- #
def fetchingInProgress(self):
""" return true if fetching repositories is still in progress """
for key in self.mRepositories:
if self.mRepositories[key]["state"] == 1:
return True
return False
# ----------------------------------------- #
def killConnection(self, key):
""" kill the fetching on demand """
if self.mRepositories[key]["state"]==1 and self.mRepositories[key]["xmlData"] and self.mRepositories[key]["xmlData"].isRunning():
self.mRepositories[key]["xmlData"].finished.disconnect()
self.mRepositories[key]["xmlData"].abort()
# ----------------------------------------- #
def xmlDownloaded(self):
""" populate the plugins object with the fetched data """
reply = self.sender()
reposName = reply.property( 'reposName' )
if reply.error() != QNetworkReply.NoError: # fetching failed
self.mRepositories[reposName]["state"] = 3
self.mRepositories[reposName]["error"] = reply.errorString()
if reply.error() == QNetworkReply.OperationCanceledError:
self.mRepositories[reposName]["error"] += "\n\n" + QCoreApplication.translate("QgsPluginInstaller", "If you haven't cancelled the download manually, it was most likely caused by a timeout. In this case consider increasing the connection timeout value in QGIS options window.")
else:
reposXML = QDomDocument()
reposXML.setContent(reply.readAll())
pluginNodes = reposXML.elementsByTagName("pyqgis_plugin")
if pluginNodes.size():
for i in range(pluginNodes.size()):
fileName = pluginNodes.item(i).firstChildElement("file_name").text().strip()
if not fileName:
fileName = QFileInfo(pluginNodes.item(i).firstChildElement("download_url").text().strip().split("?")[0]).fileName()
name = fileName.partition(".")[0]
experimental = False
if pluginNodes.item(i).firstChildElement("experimental").text().strip().upper() in ["TRUE","YES"]:
experimental = True
icon = pluginNodes.item(i).firstChildElement("icon").text().strip()
if icon and not icon.startswith("http"):
icon = "http://%s/%s" % ( QUrl(self.mRepositories[reposName]["url"]).host() , icon )
plugin = {
"id" : name,
"name" : pluginNodes.item(i).toElement().attribute("name"),
"version_available" : pluginNodes.item(i).toElement().attribute("version"),
"description" : pluginNodes.item(i).firstChildElement("description").text().strip(),
"about" : pluginNodes.item(i).firstChildElement("about").text().strip(),
"author_name" : pluginNodes.item(i).firstChildElement("author_name").text().strip(),
"homepage" : pluginNodes.item(i).firstChildElement("homepage").text().strip(),
"download_url" : pluginNodes.item(i).firstChildElement("download_url").text().strip(),
"category" : pluginNodes.item(i).firstChildElement("category").text().strip(),
"tags" : pluginNodes.item(i).firstChildElement("tags").text().strip(),
"changelog" : pluginNodes.item(i).firstChildElement("changelog").text().strip(),
"author_email" : pluginNodes.item(i).firstChildElement("author_email").text().strip(),
"tracker" : pluginNodes.item(i).firstChildElement("tracker").text().strip(),
"code_repository" : pluginNodes.item(i).firstChildElement("repository").text().strip(),
"downloads" : pluginNodes.item(i).firstChildElement("downloads").text().strip(),
"average_vote" : pluginNodes.item(i).firstChildElement("average_vote").text().strip(),
"rating_votes" : pluginNodes.item(i).firstChildElement("rating_votes").text().strip(),
"icon" : icon,
"experimental" : experimental,
"filename" : fileName,
"installed" : False,
"available" : True,
"status" : "not installed",
"error" : "",
"error_details" : "",
"version_installed" : "",
"zip_repository" : reposName,
"library" : "",
"readonly" : False
}
qgisMinimumVersion = pluginNodes.item(i).firstChildElement("qgis_minimum_version").text().strip()
if not qgisMinimumVersion: qgisMinimumVersion = "2"
qgisMaximumVersion = pluginNodes.item(i).firstChildElement("qgis_maximum_version").text().strip()
if not qgisMaximumVersion: qgisMaximumVersion = qgisMinimumVersion[0] + ".99"
#if compatible, add the plugin to the list
if not pluginNodes.item(i).firstChildElement("disabled").text().strip().upper() in ["TRUE","YES"]:
if isCompatible(QGis.QGIS_VERSION, qgisMinimumVersion, qgisMaximumVersion):
#add the plugin to the cache
plugins.addFromRepository(plugin)
self.mRepositories[reposName]["state"] = 2
else:
# no plugin metadata found
self.mRepositories[reposName]["state"] = 3
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
self.mRepositories[reposName]["error"] = QCoreApplication.translate("QgsPluginInstaller", "Server response is 200 OK, but doesn't contain plugin metatada. This is most likely caused by a proxy or a wrong repository URL. You can configure proxy settings in QGIS options.")
else:
self.mRepositories[reposName]["error"] = QCoreApplication.translate("QgsPluginInstaller", "Status code:") + " %d %s" % (
reply.attribute(QNetworkRequest.HttpStatusCodeAttribute),
reply.attribute(QNetworkRequest.HttpReasonPhraseAttribute))
self.repositoryFetched.emit( reposName )
# is the checking done?
if not self.fetchingInProgress():
self.checkingDone.emit()
reply.deleteLater()
# ----------------------------------------- #
def inspectionFilter(self):
""" return inspection filter (only one repository to be fetched) """
return self.mInspectionFilter
# ----------------------------------------- #
def setInspectionFilter(self, key = None):
""" temporarily disable all repositories but this for inspection """
self.mInspectionFilter = key
# --- /class Repositories ---------------------------------------------------------------- #
# --- class Plugins ---------------------------------------------------------------------- #
class Plugins(QObject):
""" A dict-like class for handling plugins data """
# ----------------------------------------- #
def __init__(self):
QObject.__init__(self)
self.mPlugins = {} # the dict of plugins (dicts)
self.repoCache = {} # the dict of lists of plugins (dicts)
self.localCache = {} # the dict of plugins (dicts)
self.obsoletePlugins = [] # the list of outdated 'user' plugins masking newer 'system' ones
# ----------------------------------------- #
def all(self):
""" return all plugins """
return self.mPlugins
# ----------------------------------------- #
def allUpgradeable(self):
""" return all upgradeable plugins """
result = {}
for i in self.mPlugins:
if self.mPlugins[i]["status"] == "upgradeable":
result[i] = self.mPlugins[i]
return result
# ----------------------------------------- #
def keyByUrl(self, name):
""" return plugin key by given url """
plugins = [i for i in self.mPlugins if self.mPlugins[i]["download_url"] == name]
if plugins:
return plugins[0]
return None
# ----------------------------------------- #
def clearRepoCache(self):
""" clears the repo cache before re-fetching repositories """
self.repoCache = {}
# ----------------------------------------- #
def addFromRepository(self, plugin):
""" add given plugin to the repoCache """
repo = plugin["zip_repository"]
try:
self.repoCache[repo] += [plugin]
except:
self.repoCache[repo] = [plugin]
# ----------------------------------------- #
def removeInstalledPlugin(self, key):
""" remove given plugin from the localCache """
if self.localCache.has_key(key):
del self.localCache[key]
# ----------------------------------------- #
def removeRepository(self, repo):
""" remove whole repository from the repoCache """
if self.repoCache.has_key(repo):
del self.repoCache[repo]
# ----------------------------------------- #
def getInstalledPlugin(self, key, path, readOnly, testLoad=True):
""" get the metadata of an installed plugin """
def metadataParser(fct):
""" plugin metadata parser reimplemented from qgis.utils
for better control on wchich module is examined
in case there is an installed plugin masking a core one """
metadataFile = os.path.join(path, 'metadata.txt')
if not os.path.exists(metadataFile):
return "" # plugin has no metadata.txt file
cp = ConfigParser.ConfigParser()
try:
cp.readfp(codecs.open(metadataFile, "r", "utf8"))
return cp.get('general', fct)
except:
return ""
def pluginMetadata(fct):
""" calls metadataParser for current l10n.
If failed, fallbacks to the standard metadata """
locale = QLocale.system().name()
if locale and fct in translatableAttributes:
value = metadataParser( "%s[%s]" % (fct, locale ) )
if value: return value
value = metadataParser( "%s[%s]" % (fct, locale.split("_")[0] ) )
if value: return value
return metadataParser( fct )
if not QDir(path).exists():
return
plugin = dict()
error = ""
errorDetails = ""
version = normalizeVersion( pluginMetadata("version") )
if version:
qgisMinimumVersion = pluginMetadata("qgisMinimumVersion").strip()
if not qgisMinimumVersion: qgisMinimumVersion = "0"
qgisMaximumVersion = pluginMetadata("qgisMaximumVersion").strip()
if not qgisMaximumVersion: qgisMaximumVersion = qgisMinimumVersion[0] + ".99"
#if compatible, add the plugin to the list
if not isCompatible(QGis.QGIS_VERSION, qgisMinimumVersion, qgisMaximumVersion):
error = "incompatible"
errorDetails = "%s - %s" % (qgisMinimumVersion, qgisMaximumVersion)
elif testLoad:
# only testLoad if compatible version
try:
exec "import %s" % key in globals(), locals()
exec "reload (%s)" % key in globals(), locals()
exec "%s.classFactory(iface)" % key in globals(), locals()
except Exception, error:
error = unicode(error.args[0])
except SystemExit, error:
error = QCoreApplication.translate("QgsPluginInstaller", "The plugin exited with error status: {0}").format(error.args[0])
except:
error = QCoreApplication.translate("QgsPluginInstaller", "Unknown error")
else:
# seems there is no metadata.txt file. Maybe it's an old plugin for QGIS 1.x.
version = "-1"
error = "incompatible"
errorDetails = "1.x"
if error[:16] == "No module named ":
mona = error.replace("No module named ","")
if mona != key:
error = "dependent"
errorDetails = mona
if not error in ["", "dependent", "incompatible"]:
errorDetails = error
error = "broken"
icon = pluginMetadata("icon")
if QFileInfo( icon ).isRelative():
icon = path + "/" + icon;
plugin = {
"id" : key,
"name" : pluginMetadata("name") or key,
"description" : pluginMetadata("description"),
"about" : pluginMetadata("about"),
"icon" : icon,
"category" : pluginMetadata("category"),
"tags" : pluginMetadata("tags"),
"changelog" : pluginMetadata("changelog"),
"author_name" : pluginMetadata("author_name") or pluginMetadata("author"),
"author_email" : pluginMetadata("email"),
"homepage" : pluginMetadata("homepage"),
"tracker" : pluginMetadata("tracker"),
"code_repository" : pluginMetadata("repository"),
"version_installed" : version,
"library" : path,
"pythonic" : True,
"experimental" : pluginMetadata("experimental").strip().upper() in ["TRUE","YES"],
"version_available" : "",
"zip_repository" : "",
"download_url" : path, # warning: local path as url!
"filename" : "",
"downloads" : "",
"average_vote" : "",
"rating_votes" : "",
"available" : False, # Will be overwritten, if any available version found.
"installed" : True,
"status" : "orphan", # Will be overwritten, if any available version found.
"error" : error,
"error_details" : errorDetails,
"readonly" : readOnly }
return plugin
# ----------------------------------------- #
def getAllInstalled(self, testLoad=True):
""" Build the localCache """
self.localCache = {}
# reversed list of the plugin paths: first system plugins -> then user plugins -> finally custom path(s)
pluginPaths = list(plugin_paths)
pluginPaths.reverse()
for pluginsPath in pluginPaths:
isTheSystemDir = (pluginPaths.index(pluginsPath)==0) # The curent dir is the system plugins dir
if isTheSystemDir:
# temporarily add the system path as the first element to force loading the readonly plugins, even if masked by user ones.
sys.path = [pluginsPath] + sys.path
try:
pluginDir = QDir(pluginsPath)
pluginDir.setFilter(QDir.AllDirs)
for key in pluginDir.entryList():
if not key in [".",".."]:
path = QDir.convertSeparators( pluginsPath + "/" + key )
# readOnly = not QFileInfo(pluginsPath).isWritable() # On windows testing the writable status isn't reliable.
readOnly = isTheSystemDir # Assume only the system plugins are not writable.
# only test those not yet loaded. Loaded plugins already proved they're o.k.
testLoadThis = testLoad and not qgis.utils.plugins.has_key(key)
plugin = self.getInstalledPlugin(key, path=path, readOnly=readOnly, testLoad=testLoadThis)
self.localCache[key] = plugin
if key in self.localCache.keys() and compareVersions(self.localCache[key]["version_installed"],plugin["version_installed"]) == 1:
# An obsolete plugin in the "user" location is masking a newer one in the "system" location!
self.obsoletePlugins += [key]
except:
# it's not necessary to stop if one of the dirs is inaccessible
pass
if isTheSystemDir:
# remove the temporarily added path
sys.path.remove(pluginsPath)
# ----------------------------------------- #
def rebuild(self):
""" build or rebuild the mPlugins from the caches """
self.mPlugins = {}
for i in self.localCache.keys():
self.mPlugins[i] = self.localCache[i].copy()
settings = QSettings()
allowExperimental = settings.value(settingsGroup+"/allowExperimental", False, type=bool)
for i in self.repoCache.values():
for j in i:
plugin=j.copy() # do not update repoCache elements!
key = plugin["id"]
# check if the plugin is allowed and if there isn't any better one added already.
if (allowExperimental or not plugin["experimental"]) \
and not (self.mPlugins.has_key(key) and self.mPlugins[key]["version_available"] and compareVersions(self.mPlugins[key]["version_available"], plugin["version_available"]) < 2):
# The mPlugins dict contains now locally installed plugins.
# Now, add the available one if not present yet or update it if present already.
if not self.mPlugins.has_key(key):
self.mPlugins[key] = plugin # just add a new plugin
else:
# update local plugin with remote metadata
# description, about, icon: only use remote data if local one not available. Prefer local version because of i18n.
# NOTE: don't prefer local name to not desynchronize names if repository doesn't support i18n.
# Also prefer local icon to avoid downloading.
for attrib in translatableAttributes + ["icon"]:
if attrib != "name":
if not self.mPlugins[key][attrib] and plugin[attrib]:
self.mPlugins[key][attrib] = plugin[attrib]
# other remote metadata is preffered:
for attrib in ["name", "description", "about", "category", "tags", "changelog", "author_name", "author_email", "homepage",
"tracker", "code_repository", "experimental", "version_available", "zip_repository",
"download_url", "filename", "downloads", "average_vote", "rating_votes"]:
if ( not attrib in translatableAttributes ) or ( attrib == "name" ): # include name!
if plugin[attrib]:
self.mPlugins[key][attrib] = plugin[attrib]
# set status
#
# installed available status
# ---------------------------------------
# none any "not installed" (will be later checked if is "new")
# any none "orphan"
# same same "installed"
# less greater "upgradeable"
# greater less "newer"
if not self.mPlugins[key]["version_available"]:
self.mPlugins[key]["status"] = "orphan"
elif not self.mPlugins[key]["version_installed"]:
self.mPlugins[key]["status"] = "not installed"
elif self.mPlugins[key]["version_installed"] in ["?", "-1"]:
self.mPlugins[key]["status"] = "installed"
elif compareVersions(self.mPlugins[key]["version_available"],self.mPlugins[key]["version_installed"]) == 0:
self.mPlugins[key]["status"] = "installed"
elif compareVersions(self.mPlugins[key]["version_available"],self.mPlugins[key]["version_installed"]) == 1:
self.mPlugins[key]["status"] = "upgradeable"
else:
self.mPlugins[key]["status"] = "newer"
# debug: test if the status match the "installed" tag:
if self.mPlugins[key]["status"] in ["not installed"] and self.mPlugins[key]["installed"]:
raise Exception("Error: plugin status is ambiguous (1)")
if self.mPlugins[key]["status"] in ["installed","orphan","upgradeable","newer"] and not self.mPlugins[key]["installed"]:
raise Exception("Error: plugin status is ambiguous (2)")
self.markNews()
# ----------------------------------------- #
def markNews(self):
""" mark all new plugins as new """
settings = QSettings()
seenPlugins = settings.value(seenPluginGroup, self.mPlugins.keys(), type=unicode)
if len(seenPlugins) > 0:
for i in self.mPlugins.keys():
if seenPlugins.count(i) == 0 and self.mPlugins[i]["status"] == "not installed":
self.mPlugins[i]["status"] = "new"
# ----------------------------------------- #
def updateSeenPluginsList(self):
""" update the list of all seen plugins """
settings = QSettings()
seenPlugins = settings.value(seenPluginGroup, self.mPlugins.keys(), type=unicode)
for i in self.mPlugins.keys():
if seenPlugins.count(i) == 0:
seenPlugins += [i]
settings.setValue(seenPluginGroup, seenPlugins)
# ----------------------------------------- #
def isThereAnythingNew(self):
""" return true if an upgradeable or new plugin detected """
for i in self.mPlugins.values():
if i["status"] in ["upgradeable","new"]:
return True
return False
# --- /class Plugins --------------------------------------------------------------------- #
# public instances:
repositories = Repositories()
plugins = Plugins()