mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Merge pull request #4097 from alexbruy/plugin-from-zip
[FEATURE] allow installing plugins from local ZIP packages
This commit is contained in:
commit
c30eb9e084
@ -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>
|
||||
|
89
images/themes/default/mActionInstallPluginFromZip.svg
Normal file
89
images/themes/default/mActionInstallPluginFromZip.svg
Normal 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 |
@ -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)
|
||||
|
@ -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>&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"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user