Merge pull request #4097 from alexbruy/plugin-from-zip

[FEATURE] allow installing plugins from local ZIP packages
This commit is contained in:
Borys Jurgiel 2017-02-01 18:11:38 +01:00 committed by GitHub
commit c30eb9e084
6 changed files with 199 additions and 10 deletions

View File

@ -286,6 +286,7 @@
<file>themes/default/mActionShowHideLabels.svg</file>
<file>themes/default/mActionShowPinnedLabels.svg</file>
<file>themes/default/mActionShowPluginManager.svg</file>
<file>themes/default/mActionInstallPluginFromZip.svg</file>
<file>themes/default/mActionShowRasterCalculator.png</file>
<file>themes/default/mActionShowSelectedLayers.svg</file>
<file>themes/default/mActionSimplify.svg</file>

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="24"
width="24"
id="svg4142"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="mActionInstallPluginFromZip.svg"
inkscape:export-filename="/home/alex/plugin.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<metadata
id="metadata4158">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4156" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="731"
id="namedview4154"
showgrid="false"
inkscape:zoom="9.8333333"
inkscape:cx="10.372881"
inkscape:cy="12"
inkscape:window-x="0"
inkscape:window-y="18"
inkscape:window-maximized="1"
inkscape:current-layer="svg4142" />
<g
fill="#73d216"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width=".75"
transform="translate(0 -6)"
id="g4144">
<path
d="m11.5 1c-1.380712 0-2.5 1.1192881-2.5 2.5 0 .3597817.363178.6908983.5 1l-5 0 0 5c .3091017-.1368217.6402183-.5 1-.5 1.3807119 0 2.5 1.119288 2.5 2.5 0 1.380712-1.1192881 2.5-2.5 2.5-.3597817 0-.6908983-.363178-1-.5l0 5 14 0 0-5c .309102.136822.640218.5 1 .5 1.380712 0 2.5-1.119288 2.5-2.5 0-1.380712-1.119288-2.5-2.5-2.5-.359782 0-.690898.363178-1 .5l0-5-5 0c .136822-.3091017.5-.6402183.5-1 0-1.3807119-1.119288-2.5-2.5-2.5z"
overflow="visible"
stroke="#4e9a06"
transform="translate(0 8)"
id="path4146" />
<path
d="m5.5 15.5l0-2 3 0"
overflow="visible"
stroke="#eeeeec"
id="path4148" />
<path
d="m5.5 17.5l0-2"
overflow="visible"
stroke="#eeeeec"
transform="translate(0 8)"
id="path4150" />
<path
d="m13.5 5.5l3 0 0 0 0 0"
overflow="visible"
stroke="#eeeeec"
transform="translate(0 8)"
id="path4152" />
</g>
<path
inkscape:connector-curvature="0"
id="path3782"
d="m 11.808709,13.026632 0.857159,0 0,-0.857141 -0.857159,0 0,-0.85714 0.857159,0 0,-0.857139 -0.857159,0 0,-0.8571416 0.857159,0 0,-0.857153 -0.857159,0 0,-0.8571407 0.857159,0 0,-0.8571428 -0.857159,0 0,-0.8571425 -0.857141,0 0,0.8571425 -0.857138,0 0,0.8571428 0.857138,0 0,0.8571407 -0.857138,0 0,0.857153 0.857138,0 0,0.8571416 -0.857138,0 0,0.857139 0.857138,0 0,0.85714 -0.857138,0 0,0.857141 0.857138,0 0,0.857139 -1.7142799,0 0,2.14286 c 0,1.181581 0.9612799,2.14286 2.1428609,2.14286 1.18156,0 2.142859,-0.961279 2.142859,-2.14286 l 0,-2.14286 -1.714299,0 0,-0.857139 z m 0.857159,2.999999 c 0,0.70886 -0.57686,1.28572 -1.285719,1.28572 -0.70886,0 -1.285719,-0.57686 -1.285719,-1.28572 l 0,-1.28572 2.571438,0 0,1.28572 z"
style="fill:#555753" />
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -24,8 +24,11 @@
"""
from builtins import str
from qgis.PyQt.QtCore import Qt, QObject, QSettings, QDir, QUrl
from qgis.PyQt.QtWidgets import QMessageBox, QLabel, QFrame, QApplication
import os
import zipfile
from qgis.PyQt.QtCore import Qt, QObject, QSettings, QDir, QUrl, QSettings, QFileInfo, QFile
from qgis.PyQt.QtWidgets import QMessageBox, QLabel, QFrame, QApplication, QFileDialog
from qgis.PyQt.QtNetwork import QNetworkRequest
import qgis
@ -39,6 +42,7 @@ from .qgsplugininstallerinstallingdialog import QgsPluginInstallerInstallingDial
from .qgsplugininstallerpluginerrordialog import QgsPluginInstallerPluginErrorDialog
from .qgsplugininstallerfetchingdialog import QgsPluginInstallerFetchingDialog
from .qgsplugininstallerrepositorydialog import QgsPluginInstallerRepositoryDialog
from .unzip import unzip
# public instances:
@ -345,14 +349,14 @@ class QgsPluginInstaller(QObject):
error = True
infoString = (self.tr("Plugin uninstall failed"), result)
try:
exec ("sys.path_importer_cache.clear()")
exec ("import %s" % plugin["id"])
exec ("reload (%s)" % plugin["id"])
exec("sys.path_importer_cache.clear()")
exec("import %s" % plugin["id"])
exec("reload (%s)" % plugin["id"])
except:
pass
else:
try:
exec ("del sys.modules[%s]" % plugin["id"])
exec("del sys.modules[%s]" % plugin["id"])
except:
pass
plugins.getAllInstalled()
@ -399,12 +403,12 @@ class QgsPluginInstaller(QObject):
except:
pass
try:
exec ("plugins[%s].unload()" % plugin["id"])
exec ("del plugins[%s]" % plugin["id"])
exec("plugins[%s].unload()" % plugin["id"])
exec("del plugins[%s]" % plugin["id"])
except:
pass
try:
exec ("del sys.modules[%s]" % plugin["id"])
exec("del sys.modules[%s]" % plugin["id"])
except:
pass
plugins.getAllInstalled()
@ -523,3 +527,69 @@ class QgsPluginInstaller(QObject):
req.setRawHeader("Content-Type", "application/json")
QgsNetworkAccessManager.instance().post(req, params)
return True
def installFromZipFile(self):
settings = QSettings()
lastDirectory = settings.value('/Qgis/plugin-installer/lastZipDirectory', '.')
filePath, _ = QFileDialog.getOpenFileName(iface.mainWindow(),
self.tr('Open file'),
lastDirectory,
self.tr('Plugin packages (*.zip *.ZIP)'))
if filePath == '':
return
settings.setValue('/Qgis/plugin-installer/lastZipDirectory',
QFileInfo(filePath).absoluteDir().absolutePath())
error = False
infoString = None
with zipfile.ZipFile(filePath, 'r') as zf:
pluginName = os.path.split(zf.namelist()[0])[0]
pluginFileName = os.path.splitext(os.path.basename(filePath))[0]
pluginsDirectory = qgis.utils.home_plugin_path
if not QDir(pluginsDirectory).exists():
QDir().mkpath(pluginsDirectory)
# If the target directory already exists as a link,
# remove the link without resolving
QFile(os.path.join(pluginsDirectory, pluginFileName)).remove()
try:
# Test extraction. If fails, then exception will be raised
# and no removing occurs
unzip(str(filePath), str(pluginsDirectory))
# Removing old plugin files if exist
removeDir(QDir.cleanPath(os.path.join(pluginsDirectory, pluginFileName)))
# Extract new files
unzip(str(filePath), str(pluginsDirectory))
except:
error = True
infoString = (self.tr("Plugin installation failed"),
self.tr("Failed to unzip the plugin package\n{}.\nProbably it is broken".format(zipFilePath)))
if infoString is None:
updateAvailablePlugins()
loadPlugin(pluginName)
plugins.getAllInstalled(testLoad=True)
plugins.rebuild()
plugin = plugins.all()[pluginName]
if settings.contains('/PythonPlugins/' + pluginName):
if settings.value('/PythonPlugins/' + pluginName, False, bool):
startPlugin(pluginName)
reloadPlugin(pluginName)
else:
unloadPlugin(pluginName)
loadPlugin(pluginName)
else:
if startPlugin(pluginName):
settings.setValue('/PythonPlugins/' + pluginName, True)
infoString = (self.tr("Plugin installed successfully"), "")
if infoString[0]:
level = error and QgsMessageBar.CRITICAL or QgsMessageBar.INFO
msg = "<b>%s:</b>%s" % (infoString[0], infoString[1])
iface.messageBar().pushMessage(msg, level)

View File

@ -965,11 +965,14 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
mPluginManager->setPythonUtils( mPythonUtils );
endProfile();
}
else if ( mActionShowPythonDialog )
else if ( mActionShowPythonDialog || mActionInstallFromZip )
{
// python is disabled so get rid of the action for python console
// and installing plugin from ZUIP
delete mActionShowPythonDialog;
delete mActionInstallFromZip;
mActionShowPythonDialog = nullptr;
mActionInstallFromZip = nullptr;
}
// Set icon size of toolbars
@ -1722,6 +1725,7 @@ void QgisApp::createActions()
// Plugin Menu Items
connect( mActionManagePlugins, SIGNAL( triggered() ), this, SLOT( showPluginManager() ) );
connect( mActionInstallFromZip, SIGNAL( triggered() ), this, SLOT( installPluginFromZip() ) );
connect( mActionShowPythonDialog, SIGNAL( triggered() ), this, SLOT( showPythonDialog() ) );
// Settings Menu Items
@ -2612,6 +2616,7 @@ void QgisApp::setTheme( const QString& theThemeName )
mActionToggleFullScreen->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionToggleFullScreen.png" ) ) );
mActionProjectProperties->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionProjectProperties.png" ) ) );
mActionManagePlugins->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionShowPluginManager.svg" ) ) );
mActionInstallFromZip->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionInstallPluginFromZip.svg" ) ) );
mActionShowPythonDialog->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "console/iconRunConsole.png" ) ) );
mActionCheckQgisVersion->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconSuccess.svg" ) ) );
mActionOptions->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOptions.svg" ) ) );
@ -8909,6 +8914,15 @@ void QgisApp::showPluginManager()
}
}
void QgisApp::installPluginFromZip()
{
if ( mPythonUtils && mPythonUtils->isEnabled() )
{
QgsPythonRunner::run( QStringLiteral( "pyplugin_installer.instance().installFromZipFile()" ) );
}
}
// implementation of the python runner
class QgsPythonRunnerImpl : public QgsPythonRunner
{

View File

@ -863,6 +863,11 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
void showPluginManager();
//! load python support if possible
void loadPythonSupport();
/** Install plugin from ZIP file
* @note added in QGIS 3.0
*/
void installPluginFromZip();
//! Find the QMenu with the given name within plugin menu (ie the user visible text on the menu item)
QMenu* getPluginMenu( const QString& menuName );
//! Add the action to the submenu with the given name under the plugin menu

View File

@ -187,6 +187,7 @@
<string>&amp;Plugins</string>
</property>
<addaction name="mActionManagePlugins"/>
<addaction name="mActionInstallFromZip"/>
<addaction name="separator"/>
<addaction name="mActionShowPythonDialog"/>
</widget>
@ -2591,6 +2592,15 @@ Acts on currently active editable layer</string>
<string>Copy and Move Feature(s)</string>
</property>
</action>
<action name="mActionInstallFromZip">
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionInstallPluginFromZip.svg</normaloff>:/images/themes/default/mActionInstallPluginFromZip.svg</iconset>
</property>
<property name="text">
<string>Install plugin from ZIP...</string>
</property>
</action>
</widget>
<resources>
<include location="../../images/images.qrc"/>