diff --git a/images/images.qrc b/images/images.qrc
index c43132fd74a..c71b40617cd 100644
--- a/images/images.qrc
+++ b/images/images.qrc
@@ -286,6 +286,7 @@
themes/default/mActionShowHideLabels.svg
themes/default/mActionShowPinnedLabels.svg
themes/default/mActionShowPluginManager.svg
+ themes/default/mActionInstallPluginFromZip.svg
themes/default/mActionShowRasterCalculator.png
themes/default/mActionShowSelectedLayers.svg
themes/default/mActionSimplify.svg
diff --git a/images/themes/default/mActionInstallPluginFromZip.svg b/images/themes/default/mActionInstallPluginFromZip.svg
new file mode 100644
index 00000000000..ee332d9a86b
--- /dev/null
+++ b/images/themes/default/mActionInstallPluginFromZip.svg
@@ -0,0 +1,89 @@
+
+
diff --git a/python/pyplugin_installer/installer.py b/python/pyplugin_installer/installer.py
index 2acb6adad61..caed3cda195 100644
--- a/python/pyplugin_installer/installer.py
+++ b/python/pyplugin_installer/installer.py
@@ -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 = "%s:%s" % (infoString[0], infoString[1])
+ iface.messageBar().pushMessage(msg, level)
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index fa4845c6f7f..8f383d60273 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -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
{
diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h
index 4a1406a100f..a151ac07f11 100644
--- a/src/app/qgisapp.h
+++ b/src/app/qgisapp.h
@@ -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
diff --git a/src/ui/qgisapp.ui b/src/ui/qgisapp.ui
index 28765ca7c6c..9f9b64f6914 100644
--- a/src/ui/qgisapp.ui
+++ b/src/ui/qgisapp.ui
@@ -187,6 +187,7 @@
&Plugins
+
@@ -2591,6 +2592,15 @@ Acts on currently active editable layer
Copy and Move Feature(s)
+
+
+
+ :/images/themes/default/mActionInstallPluginFromZip.svg:/images/themes/default/mActionInstallPluginFromZip.svg
+
+
+ Install plugin from ZIP...
+
+