Skip to content
Permalink
Browse files

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

[FEATURE] allow installing plugins from local ZIP packages
  • Loading branch information
borysiasty committed Feb 1, 2017
2 parents e6b38af + afe85ca commit c30eb9e08426d6e978170725db0a69d610831a42
@@ -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>
@@ -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>
@@ -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 .qgsplugininstallerpluginerrordialog import QgsPluginInstallerPluginErrorDialog
from .qgsplugininstallerfetchingdialog import QgsPluginInstallerFetchingDialog
from .qgsplugininstallerrepositorydialog import QgsPluginInstallerRepositoryDialog
from .unzip import unzip


# public instances:
@@ -345,14 +349,14 @@ def installPlugin(self, key, quiet=False):
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 @@ def uninstallPlugin(self, key, quiet=False):
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 @@ def sendVote(self, plugin_id, vote):
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)
@@ -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
{
@@ -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
@@ -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"/>

0 comments on commit c30eb9e

Please sign in to comment.
You can’t perform that action at this time.