Skip to content

Commit

Permalink
[FEATURE] allow installing plugins from local ZIP packages
Browse files Browse the repository at this point in the history
This may be useful for users who have plugin packages, so they
don't need to extract them manually.
  • Loading branch information
alexbruy committed Feb 1, 2017
1 parent 3609c86 commit afe85ca
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 10 deletions.
1 change: 1 addition & 0 deletions images/images.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
89 changes: 89 additions & 0 deletions images/themes/default/mActionInstallPluginFromZip.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 79 additions & 9 deletions python/pyplugin_installer/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -39,6 +42,7 @@
from .qgsplugininstallerpluginerrordialog import QgsPluginInstallerPluginErrorDialog
from .qgsplugininstallerfetchingdialog import QgsPluginInstallerFetchingDialog
from .qgsplugininstallerrepositorydialog import QgsPluginInstallerRepositoryDialog
from .unzip import unzip


# public instances:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
16 changes: 15 additions & 1 deletion src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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" ) ) );
Expand Down Expand Up @@ -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
{
Expand Down
5 changes: 5 additions & 0 deletions src/app/qgisapp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/ui/qgisapp.ui
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@
<string>&amp;Plugins</string>
</property>
<addaction name="mActionManagePlugins"/>
<addaction name="mActionInstallFromZip"/>
<addaction name="separator"/>
<addaction name="mActionShowPythonDialog"/>
</widget>
Expand Down Expand Up @@ -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"/>
Expand Down

0 comments on commit afe85ca

Please sign in to comment.