Skip to content

Commit 9c5c257

Browse files
committed
[processing] first implementation of github-based resources manager
1 parent 6c9f7d7 commit 9c5c257

8 files changed

+497
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
GetScriptsAndModels.py
6+
---------------------
7+
Date : June 2014
8+
Copyright : (C) 2014 by Victor Olaya
9+
Email : volayaf at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
import os
20+
from PyQt4 import QtGui
21+
from processing.ui.ui_DlgGetScriptsAndModels import Ui_DlgGetScriptsAndModels
22+
from processing.script.ScriptUtils import ScriptUtils
23+
from processing.modeler.ModelerUtils import ModelerUtils
24+
import json
25+
from processing.gui import Help2Html
26+
from qgis.utils import iface
27+
from processing.gui.Help2Html import getDescription, ALG_DESC, ALG_VERSION,\
28+
ALG_CREATOR
29+
30+
__author__ = 'Victor Olaya'
31+
__date__ = 'June 2014'
32+
__copyright__ = '(C) 201, Victor Olaya'
33+
34+
# This will get replaced with a git SHA1 when you do a git archive
35+
36+
__revision__ = '$Format:%H$'
37+
38+
import urllib2
39+
from urllib2 import HTTPError
40+
from PyQt4.QtCore import *
41+
from PyQt4.QtGui import *
42+
from processing.gui.ToolboxAction import ToolboxAction
43+
44+
45+
class GetScriptsAction(ToolboxAction):
46+
47+
def __init__(self):
48+
self.name = "Get scripts from on-line scripts collection"
49+
self.group = 'Tools'
50+
51+
def getIcon(self):
52+
return QIcon(':/processing/images/script.png')
53+
54+
def execute(self):
55+
try:
56+
dlg = GetScriptsAndModelsDialog(GetScriptsAndModelsDialog.SCRIPTS)
57+
dlg.exec_()
58+
if dlg.updateToolbox:
59+
self.toolbox.updateProvider('script')
60+
61+
except HTTPError:
62+
QMessageBox.critical(iface.mainWindow(), "Connection problem", "Could not connect to scripts/models repository")
63+
64+
class GetModelsAction(ToolboxAction):
65+
66+
def __init__(self):
67+
self.name = "Get models from on-line scripts collection"
68+
self.group = 'Tools'
69+
70+
def getIcon(self):
71+
return QIcon(':/processing/images/model.png')
72+
73+
def execute(self):
74+
try:
75+
dlg = GetScriptsAndModelsDialog(GetScriptsAndModelsDialog.MODELS)
76+
dlg.exec_()
77+
if dlg.updateToolbox:
78+
self.toolbox.updateProvider('model')
79+
except HTTPError:
80+
QMessageBox.critical(iface.mainWindow(), "Connection problem", "Could not connect to scripts/models repository")
81+
82+
83+
def readUrl(url):
84+
try:
85+
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
86+
return urllib2.urlopen(url).read()
87+
finally:
88+
QApplication.restoreOverrideCursor()
89+
90+
91+
class GetScriptsAndModelsDialog(QDialog, Ui_DlgGetScriptsAndModels):
92+
93+
HELP_TEXT = ("<h3> Processing resources manager </h3>"
94+
"<p>Check/uncheck algorithms in the tree to select the ones that you want to install or remove</p>"
95+
"<p>Algorithms are divided in 3 groups:</p>"
96+
"<ul><li><b>Installed:</b> Algorihms already in your system, with the latest version available</li>"
97+
"<li><b>Upgradable:</b> Algorihms already in your system, but with a newer version available in the server</li>"
98+
"<li><b>Not installed:</b> Algorithms not installed in your system</li></ul>")
99+
MODELS = 0
100+
SCRIPTS = 1
101+
102+
def __init__(self, resourceType):
103+
QDialog.__init__(self, iface.mainWindow())
104+
self.resourceType = resourceType
105+
if self.resourceType == self.MODELS:
106+
self.folder = ModelerUtils.modelsFolder()
107+
self.urlBase = "https://raw.githubusercontent.com/qgis/QGIS-Processing/master/models/"
108+
self.icon = QtGui.QIcon(os.path.dirname(__file__) + '/../images/model.png')
109+
else:
110+
self.folder = ScriptUtils.scriptsFolder()
111+
self.urlBase = "https://raw.githubusercontent.com/qgis/QGIS-Processing/master/scripts/"
112+
self.icon = QtGui.QIcon(os.path.dirname(__file__) + '/../images/script.png')
113+
self.lastSelectedItem = None
114+
self.setupUi(self)
115+
self.populateTree()
116+
self.updateToolbox = False
117+
self.buttonBox.accepted.connect(self.okPressed)
118+
self.buttonBox.rejected.connect(self.cancelPressed)
119+
self.tree.currentItemChanged .connect(self.currentItemChanged)
120+
121+
def populateTree(self):
122+
self.uptodateItem = QTreeWidgetItem()
123+
self.uptodateItem.setText(0, "Installed")
124+
self.toupdateItem = QTreeWidgetItem()
125+
self.toupdateItem.setText(0, "Upgradable")
126+
self.notinstalledItem = QTreeWidgetItem()
127+
self.notinstalledItem.setText(0, "Not installed")
128+
self.toupdateItem.setIcon(0, self.icon)
129+
self.uptodateItem.setIcon(0, self.icon)
130+
self.notinstalledItem.setIcon(0, self.icon)
131+
resources = readUrl(self.urlBase + "list.txt").splitlines()
132+
resources = [r.split(",") for r in resources]
133+
for filename, version, name in resources:
134+
treeBranch = self.getTreeBranchForState(filename, float(version))
135+
item = TreeItem(filename, name, self.icon)
136+
treeBranch.addChild(item)
137+
if treeBranch != self.notinstalledItem:
138+
item.setCheckState(0, Qt.Checked)
139+
140+
self.tree.addTopLevelItem(self.toupdateItem)
141+
self.tree.addTopLevelItem(self.notinstalledItem)
142+
self.tree.addTopLevelItem(self.uptodateItem)
143+
144+
self.webView.setHtml(self.HELP_TEXT)
145+
146+
def currentItemChanged(self, item, prev):
147+
if isinstance(item, TreeItem):
148+
try:
149+
url = self.urlBase + item.filename.replace(" ","%20") + ".help"
150+
helpContent = readUrl(url)
151+
descriptions = json.loads(helpContent)
152+
html = "<h2>%s</h2>" % item.name
153+
html+="<p><b>Description:</b> " + getDescription(ALG_DESC, descriptions)+"</p>"
154+
html+="<p><b>Created by:</b> " + getDescription(ALG_CREATOR, descriptions)+"</p>"
155+
html+="<p><b>Version:</b> " + getDescription(ALG_VERSION, descriptions)+"</p>"
156+
except HTTPError, e:
157+
html = "<h2>No detailed description available for this script</h2>"
158+
self.webView.setHtml(html)
159+
else:
160+
self.webView.setHtml(self.HELP_TEXT)
161+
162+
def getTreeBranchForState(self, filename, version):
163+
if not os.path.exists(os.path.join(self.folder, filename)):
164+
return self.notinstalledItem
165+
else:
166+
helpFile = os.path.join(self.folder, filename + ".help")
167+
if not os.path.exists(helpFile):
168+
currentVersion = 1
169+
else:
170+
with open(helpFile) as f:
171+
helpContent = json.load(f)
172+
try:
173+
currentVersion = float(helpContent[Help2Html.ALG_VERSION])
174+
except:
175+
currentVersion = 1
176+
print filename, currentVersion, version
177+
if version > currentVersion:
178+
print version - currentVersion
179+
return self.toupdateItem
180+
else:
181+
return self.uptodateItem
182+
183+
184+
def cancelPressed(self):
185+
self.close()
186+
187+
def okPressed(self):
188+
toDownload = []
189+
for i in xrange(self.toupdateItem.childCount()):
190+
item = self.toupdateItem.child(i)
191+
if item.checkState(0) == Qt.Checked:
192+
toDownload.append(item.filename)
193+
for i in xrange(self.notinstalledItem.childCount()):
194+
item = self.notinstalledItem.child(i)
195+
if item.checkState(0) == Qt.Checked:
196+
toDownload.append(item.filename)
197+
198+
if toDownload:
199+
self.progressBar.setMaximum(len(toDownload))
200+
for i, filename in enumerate(toDownload):
201+
QCoreApplication.processEvents()
202+
url = self.urlBase + filename.replace(" ","%20")
203+
code = readUrl(url)
204+
path = os.path.join(self.folder, filename)
205+
with open(path, "w") as f:
206+
f.write(code)
207+
self.progressBar.setValue(i + 1)
208+
209+
toDelete = []
210+
for i in xrange(self.uptodateItem.childCount()):
211+
item = self.uptodateItem.child(i)
212+
if item.checkState(0) == Qt.Unchecked:
213+
toDelete.append(item.filename)
214+
for filename in toDelete:
215+
path = os.path.join(self.folder, filename)
216+
os.remove(path)
217+
218+
self.updateToolbox = len(toDownload) + len(toDelete)> 0
219+
self.close()
220+
221+
222+
class TreeItem(QTreeWidgetItem):
223+
224+
def __init__(self, filename, name, icon):
225+
QTreeWidgetItem.__init__(self)
226+
self.name = name
227+
self.filename = filename
228+
self.setText(0, name)
229+
self.setIcon(0, icon)
230+
self.setCheckState(0, Qt.Unchecked)
231+
232+
233+
234+
235+
236+
237+

python/plugins/processing/gui/Help2Html.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
ALG_DESC = 'ALG_DESC'
3434
ALG_CREATOR = 'ALG_CREATOR'
3535
ALG_HELP_CREATOR = 'ALG_HELP_CREATOR'
36+
ALG_VERSION = 'ALG_VERSION'
3637

3738
exps = [(r"\*(.*?)\*", r"<i>\1</i>"),
3839
("``(.*?)``", r'<FONT FACE="courier">\1</FONT>'),
@@ -56,7 +57,6 @@ def getHtmlFromRstFile(rst):
5657
def getHtmlFromHelpFile(alg, helpFile):
5758
if not os.path.exists(helpFile):
5859
return None
59-
alg = alg
6060
with open(helpFile) as f:
6161
descriptions = json.load(f)
6262
s = '<html><body><h2>Algorithm description</h2>\n'
@@ -72,6 +72,7 @@ def getHtmlFromHelpFile(alg, helpFile):
7272
s += '<br>'
7373
s += '<p align="right">Algorithm author: ' + getDescription(ALG_CREATOR, descriptions) + '</p>'
7474
s += '<p align="right">Help author: ' + getDescription(ALG_HELP_CREATOR, descriptions) + '</p>'
75+
s += '<p align="right">Algorithm version: ' + getDescription(ALG_VERSION, descriptions) + '</p>'
7576
s += '</body></html>'
7677
return s
7778

@@ -80,3 +81,4 @@ def getDescription(name, descriptions):
8081
return descriptions[name].replace("\n", "<br>")
8182
else:
8283
return ''
84+

python/plugins/processing/gui/HelpEditionDialog.py

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class HelpEditionDialog(QDialog, Ui_DlgHelpEdition):
4040
ALG_DESC = 'ALG_DESC'
4141
ALG_CREATOR = 'ALG_CREATOR'
4242
ALG_HELP_CREATOR = 'ALG_HELP_CREATOR'
43+
ALG_VERSION = 'ALG_VERSION'
4344

4445
def __init__(self, alg):
4546
QDialog.__init__(self)
@@ -117,6 +118,9 @@ def fillTree(self):
117118
item = TreeDescriptionItem('Algorithm help written by',
118119
self.ALG_HELP_CREATOR)
119120
self.tree.addTopLevelItem(item)
121+
item = TreeDescriptionItem('Algorithm version',
122+
self.ALG_VERSION)
123+
self.tree.addTopLevelItem(item)
120124

121125
def changeItem(self):
122126
item = self.tree.currentItem()

python/plugins/processing/modeler/ModelerAlgorithmProvider.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,14 @@
4040
from processing.modeler.CreateNewModelAction import CreateNewModelAction
4141
from processing.modeler.DeleteModelAction import DeleteModelAction
4242
from processing.modeler.AddModelFromFileAction import AddModelFromFileAction
43+
from processing.gui.GetScriptsAndModels import GetModelsAction
4344

4445

4546
class ModelerAlgorithmProvider(AlgorithmProvider):
4647

4748
def __init__(self):
4849
AlgorithmProvider.__init__(self)
49-
self.actions = [CreateNewModelAction(), AddModelFromFileAction()]
50+
self.actions = [CreateNewModelAction(), AddModelFromFileAction(), GetModelsAction()]
5051
self.contextMenuActions = [EditModelAction(), DeleteModelAction(),
5152
SaveAsPythonScriptAction()]
5253

python/plugins/processing/script/ScriptAlgorithmProvider.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from processing.script.ScriptUtils import ScriptUtils
3939
from processing.script.WrongScriptException import WrongScriptException
4040
from processing.script.AddScriptFromFileAction import AddScriptFromFileAction
41-
41+
from processing.gui.GetScriptsAndModels import GetScriptsAction
4242
import processing.resources_rc
4343

4444

@@ -48,7 +48,8 @@ def __init__(self):
4848
AlgorithmProvider.__init__(self)
4949
self.actions.extend([CreateNewScriptAction('Create new script',
5050
CreateNewScriptAction.SCRIPT_PYTHON),
51-
AddScriptFromFileAction()])
51+
AddScriptFromFileAction(),
52+
GetScriptsAction()])
5253
self.contextMenuActions = \
5354
[EditScriptAction(EditScriptAction.SCRIPT_PYTHON),
5455
DeleteScriptAction(DeleteScriptAction.SCRIPT_PYTHON)]

python/plugins/processing/script/scripts/Unique_values_count.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
##[Example scripts]aster processing=group
1+
##[Example scripts]=group
22
##input=raster
33
##round_values_to_ndigits=number 3
44
##output_file=output html

0 commit comments

Comments
 (0)