fixed #6571, #6619 and problem with missing arrows in modeler

added first draft of geoserver tools
nviz is back in grass tools
This commit is contained in:
Victor Olaya 2012-11-05 20:07:16 +01:00
parent a4e4999e57
commit 983535fa58
46 changed files with 4203 additions and 278 deletions

4
.gitignore vendored
View File

@ -45,4 +45,6 @@ qgis-test.ctest
i18n/*.qm
.project
.pydevproject
.idea
.idea
/python/plugins/sextante/resources_rc.py
/python/plugins/sextante/about/ui_aboutdialogbase.py

View File

@ -16,6 +16,7 @@
* *
***************************************************************************
"""
from sextante.servertools.GeoServerToolsAlgorithmProvider import GeoServerToolsAlgorithmProvider
__author__ = 'Victor Olaya'
@ -130,6 +131,7 @@ class Sextante:
Sextante.addProvider(GrassAlgorithmProvider())
Sextante.addProvider(ScriptAlgorithmProvider())
Sextante.addProvider(TauDEMAlgorithmProvider())
Sextante.addProvider(GeoServerToolsAlgorithmProvider())
Sextante.modeler.initializeSettings();
#and initialize
SextanteLog.startLogging()

View File

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
DatabaseToolProvider.py
---------------------
Date : October 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Victor Olaya'
__date__ = 'October 2012'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from sextante.core.AlgorithmProvider import AlgorithmProvider
from PyQt4 import QtGui
import os
class DatabaseToolsAlgorithmProvider(AlgorithmProvider):
def __init__(self):
AlgorithmProvider.__init__(self)
self.alglist = []#PostGISSQL(), ImportIntoPostGIS(), CreateTable()]
def initializeSettings(self):
AlgorithmProvider.initializeSettings(self)
def unload(self):
AlgorithmProvider.unload(self)
def getName(self):
return "database"
def getDescription(self):
return "Database tools"
def getIcon(self):
return QtGui.QIcon(os.path.dirname(__file__) + "/../images/postgis.png")
def _loadAlgorithms(self):
self.algs = self.alglist
def supportsNonFileBasedOutput(self):
return True

View File

@ -16,6 +16,7 @@
* *
***************************************************************************
"""
from sextante.parameters.ParameterString import ParameterString
__author__ = 'Victor Olaya'
__date__ = 'August 2012'
@ -34,6 +35,7 @@ class translate(GeoAlgorithm):
INPUT = "INPUT"
OUTPUT = "OUTPUT"
EXTRA = "EXTRA"
def getIcon(self):
filepath = os.path.dirname(__file__) + "/icons/translate.png"
@ -43,14 +45,18 @@ class translate(GeoAlgorithm):
self.name = "translate"
self.group = "Conversion"
self.addParameter(ParameterRaster(translate.INPUT, "Input layer", False))
self.addParameter(ParameterString(translate.EXTRA, "Additional creation parameters"))
self.addOutput(OutputRaster(translate.OUTPUT, "Output layer"))
def processAlgorithm(self, progress):
commands = ["gdal_translate"]
commands.append("-of")
out = self.getOutputValue(translate.OUTPUT)
extra = self.getOutputValue(translate.EXTRA)
commands.append(GdalUtils.getFormatShortNameFromFilename(out))
commands.append(extra)
commands.append(self.getParameterValue(translate.INPUT))
commands.append(out)
GdalUtils.runGdal(commands, progress)

View File

@ -16,6 +16,7 @@
* *
***************************************************************************
"""
from sextante.grass.nviz import nviz
__author__ = 'Victor Olaya'
__date__ = 'August 2012'
@ -37,10 +38,6 @@ class GrassAlgorithmProvider(AlgorithmProvider):
def __init__(self):
AlgorithmProvider.__init__(self)
#=======================================================================
# self.actions.append(DefineGrassRegionAction())
# self.actions.append(DefineGrassRegionFromLayerAction())
#=======================================================================
self.createAlgsList() #preloading algorithms to speed up
def initializeSettings(self):
@ -76,7 +73,7 @@ class GrassAlgorithmProvider(AlgorithmProvider):
SextanteLog.addToLog(SextanteLog.LOG_ERROR, "Could not open GRASS algorithm: " + descriptionFile)
except Exception,e:
SextanteLog.addToLog(SextanteLog.LOG_ERROR, "Could not open GRASS algorithm: " + descriptionFile)
#self.preloadedAlgs.append(nviz())
self.preloadedAlgs.append(nviz())
def _loadAlgorithms(self):
self.algs = self.preloadedAlgs

View File

@ -54,7 +54,6 @@ from sextante.outputs.OutputHTML import OutputHTML
from sextante.outputs.OutputRaster import OutputRaster
from sextante.outputs.OutputTable import OutputTable
from sextante.outputs.OutputVector import OutputVector
from sextante.outputs.OutputNumber import OutputNumber
from sextante.parameters.ParameterString import ParameterString
class ParametersPanel(QtGui.QWidget):

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

View File

@ -16,6 +16,7 @@
* *
***************************************************************************
"""
from sextante.outputs.OutputString import OutputString
__author__ = 'Victor Olaya'
__date__ = 'August 2012'
@ -436,6 +437,8 @@ class ModelerAlgorithm(GeoAlgorithm):
return "output html"
elif isinstance(out, OutputNumber):
return "output number"
elif isinstance(out, OutputString):
return "output string"
def getAsPythonCode(self):

View File

@ -64,10 +64,14 @@ class ModelerArrowItem(QtGui.QGraphicsLineItem):
return self.myEndItem
def boundingRect(self):
extra = (self.pen().width() + 20) / 2.0
p1 = self.line().p1()
p2 = self.line().p2()
return QtCore.QRectF(p1, QtCore.QSizeF(p2.x() - p1.x(), p2.y() - p1.y())).normalized().adjusted(-extra, -extra, extra, extra)
#this is a quick fix to avoid arrows not being drawn
return QtCore.QRectF(0, 0, 4000,4000)
#=======================================================================
# extra = (self.pen().width() + 20) / 2.0
# p1 = self.line().p1()
# p2 = self.line().p2()
# return QtCore.QRectF(p1, QtCore.QSizeF(p2.x() - p1.x(), p2.y() - p1.y())).normalized().adjusted(-extra, -extra, extra, extra)
#=======================================================================
def shape(self):
path = super(ModelerArrowItem, self).shape()
@ -123,3 +127,4 @@ class ModelerArrowItem(QtGui.QGraphicsLineItem):
painter.drawLine(line)
painter.drawPolygon(self.arrowHead)

View File

@ -17,6 +17,7 @@
***************************************************************************
"""
from sextante.parameters.ParameterCrs import ParameterCrs
from sextante.outputs.OutputString import OutputString
__author__ = 'Victor Olaya'
__date__ = 'August 2012'
@ -317,6 +318,20 @@ class ModelerParametersDialog(QtGui.QDialog):
for param in params:
if isinstance(param, ParameterString):
strings.append(AlgorithmAndParameter(AlgorithmAndParameter.PARENT_MODEL_ALGORITHM, param.name, "", param.description))
if self.algIndex is None:
dependent = []
else:
dependent = self.model.getDependentAlgorithms(self.algIndex)
dependent.append(self.algIndex)
i=0
for alg in self.model.algs:
if i not in dependent:
for out in alg.outputs:
if isinstance(out, OutputString):
strings.append(AlgorithmAndParameter(i, out.name, alg.name, out.description))
i+=1
return strings
def getTableFields(self):

View File

@ -37,6 +37,7 @@ class ModelerScene(QtGui.QGraphicsScene):
super(ModelerScene, self).__init__(parent)
self.paramItems = []
self.algItems = []
self.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex);
def getParameterPositions(self):
pos = []
@ -116,8 +117,7 @@ class ModelerScene(QtGui.QGraphicsScene):
for sourceItem in sourceItems:
arrow = ModelerArrowItem(sourceItem, self.algItems[iAlg])
self.addItem(arrow)
iAlg+=1
iAlg+=1
def mousePressEvent(self, mouseEvent):
if (mouseEvent.button() != QtCore.Qt.LeftButton):

View File

@ -24,7 +24,6 @@ __copyright__ = '(C) 2012, Victor Olaya'
__revision__ = '$Format:%H$'
import os
from sextante.core.SextanteUtils import SextanteUtils
from sextante.core.SextanteUtils import mkdir
from sextante.core.SextanteConfig import SextanteConfig
@ -37,8 +36,7 @@ class ModelerUtils:
def modelsFolder():
folder = SextanteConfig.getSetting(ModelerUtils.MODELS_FOLDER)
if folder == None:
#folder = os.path.join(os.path.dirname(__file__), "models")
folder = SextanteUtils.userFolder() + os.sep + "models"
folder = os.path.join(os.path.dirname(__file__), "models")
mkdir(folder)
return folder

View File

@ -0,0 +1,75 @@
NAME:Watersheds from DEM
GROUP:[Sample models]
PARAMETER:ParameterRaster|RASTERLAYER_DEM|DEM|False
458.0,50.0
PARAMETER:ParameterNumber|NUMBER_INITIATIONTHRESHOLD|Initiation Threshold|None|None|10000000.0
257.0,403.0
VALUE:HARDCODEDPARAMVALUE_INIT_VALUE_1===10000000
VALUE:HARDCODEDPARAMVALUE_SPLIT_3===0
VALUE:HARDCODEDPARAMVALUE_Method_0===0
VALUE:HARDCODEDPARAMVALUE_INIT_METHOD_1===2
VALUE:HARDCODEDPARAMVALUE_CALC_METHOD_4===0
VALUE:HARDCODEDPARAMVALUE_CLASS_ID_3===0
VALUE:HARDCODEDPARAMVALUE_STEP_0===1
VALUE:HARDCODEDPARAMVALUE_MINLEN_1===10
VALUE:HARDCODEDPARAMVALUE_DOLINEAR _0===True
VALUE:HARDCODEDPARAMVALUE_MINSIZE_2===0
VALUE:HARDCODEDPARAMVALUE_CLASS_ALL_3===1
VALUE:HARDCODEDPARAMVALUE_LINEARTHRS_0===500.0
VALUE:HARDCODEDPARAMVALUE_CONVERGENCE_0===1.0
VALUE:HARDCODEDPARAMVALUE_DIV_CELLS_1===10
ALGORITHM:saga:catchmentarea(parallel)
260.0,172.0
-1|RASTERLAYER_DEM
None
None
None
None
-1|HARDCODEDPARAMVALUE_STEP_0
-1|HARDCODEDPARAMVALUE_Method_0
-1|HARDCODEDPARAMVALUE_DOLINEAR _0
-1|HARDCODEDPARAMVALUE_LINEARTHRS_0
None
None
-1|HARDCODEDPARAMVALUE_CONVERGENCE_0
None
None
None
None
None
None
None
None
ALGORITHM:saga:channelnetwork
447.0,291.0
-1|RASTERLAYER_DEM
None
0|CAREA
-1|HARDCODEDPARAMVALUE_INIT_METHOD_1
-1|NUMBER_INITIATIONTHRESHOLD
None
-1|HARDCODEDPARAMVALUE_DIV_CELLS_1
None
-1|HARDCODEDPARAMVALUE_MINLEN_1
None
None
None
ALGORITHM:saga:watershedbasins
730.0,182.0
-1|RASTERLAYER_DEM
1|CHNLNTWRK
None
-1|HARDCODEDPARAMVALUE_MINSIZE_2
None
ALGORITHM:saga:vectorisinggridclasses
864.0,330.0
2|BASINS
-1|HARDCODEDPARAMVALUE_CLASS_ALL_3
-1|HARDCODEDPARAMVALUE_CLASS_ID_3
-1|HARDCODEDPARAMVALUE_SPLIT_3
None
ALGORITHM:ftools:export/addgeometrycolumns
655.0,442.0
3|POLYGONS
-1|HARDCODEDPARAMVALUE_CALC_METHOD_4
Watersheds

View File

@ -16,7 +16,6 @@
* *
***************************************************************************
"""
__author__ = 'Victor Olaya'
__date__ = 'August 2012'
__copyright__ = '(C) 2012, Victor Olaya'
@ -29,12 +28,13 @@ from sextante.outputs.OutputTable import OutputTable
from sextante.outputs.OutputVector import OutputVector
from sextante.outputs.OutputNumber import OutputNumber
from sextante.outputs.OutputFile import OutputFile
from sextante.outputs.OutputString import OutputString
class OutputFactory():
@staticmethod
def getFromString(s):
classes = [OutputRaster, OutputVector, OutputTable, OutputHTML, OutputNumber, OutputFile]
classes = [OutputRaster, OutputVector, OutputTable, OutputHTML, OutputNumber, OutputFile, OutputString]
for clazz in classes:
if s.startswith(clazz().outputTypeName()):
tokens = s[len(clazz().outputTypeName())+1:].split("|")

View File

@ -2,11 +2,11 @@
"""
***************************************************************************
__init__.py
OutputString.py
---------------------
Date : August 2012
Copyright : (C) 2012 by Tim Sutton
Email : tim at linfiniti dot com
Date : October 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
@ -17,22 +17,18 @@
***************************************************************************
"""
__author__ = 'Tim Sutton'
__author__ = 'Victor Olaya'
__date__ = 'August 2012'
__copyright__ = '(C) 2012, Tim Sutton'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
def name():
return "SEXTANTE example provider"
def description():
return "An example plugin that adds algorithms to SEXTANTE. Mainly created to guide developers in the process of creating plugins that add new capabilities to SEXTANTE"
def version():
return "Version 1.0"
def icon():
return "icon.png"
def qgisMinimumVersion():
return "1.0"
def classFactory(iface):
from sextanteexampleprovider.SextanteExampleProviderPlugin import SextanteExampleProviderPlugin
return SextanteExampleProviderPlugin()
from sextante.outputs.Output import Output
class OutputString(Output):
def __init__(self, name="", description=""):
self.name = name
self.description = description
self.value = None
self.hidden = True

View File

@ -36,7 +36,7 @@ class PymorphAlgorithmProvider(AlgorithmProvider):
def __init__(self):
AlgorithmProvider.__init__(self)
#self.readAlgNames()
self.activate = False
self.createAlgsList()
def scriptsFolder(self):

View File

@ -88,9 +88,11 @@ class EditRScriptDialog(QtGui.QDialog):
def saveAlgorithm(self):
if self.filename is None:
self.filename = QtGui.QFileDialog.getSaveFileName(self, "Save Script", RUtils.RScriptsFolder(), "SEXTANTE R script (*.rsx)")
self.filename = QtGui.QFileDialog.getSaveFileName(self, "Save Script", RUtils.RScriptsFolder(), "SEXTANTE R script (*.rsx)")
if self.filename:
if not self.filename.endswith(".rsx"):
self.filename += ".rsx"
text = str(self.text.toPlainText())
if self.alg is not None:
self.alg.script = text

View File

@ -91,6 +91,8 @@ class EditScriptDialog(QtGui.QDialog):
self.filename = QtGui.QFileDialog.getSaveFileName(self, "Save Script", ScriptUtils.scriptsFolder(), "Python scripts (*.py)")
if self.filename:
if not self.filename.endswith(".py"):
self.filename += ".py"
text = str(self.text.toPlainText())
if self.alg is not None:
self.alg.script = text

View File

@ -0,0 +1,61 @@
#===============================================================================
# # -*- coding: utf-8 -*-
#
# """
# ***************************************************************************
# CreateMosaicDatastore.py
# ---------------------
# Date : October 2012
# Copyright : (C) 2012 by Victor Olaya
# Email : volayaf at gmail dot com
# ***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation; either version 2 of the License, or *
# * (at your option) any later version. *
# * *
# ***************************************************************************
# """
# from sextante.core.LayerExporter import LayerExporter
# from sextante.parameters.ParameterString import ParameterString
# from sextante.servertools.GeoServerToolsAlgorithm import GeoServerToolsAlgorithm
#
# __author__ = 'Victor Olaya'
# __date__ = 'October 2012'
# __copyright__ = '(C) 2012, Victor Olaya'
# # This will get replaced with a git SHA1 when you do a git archive
# __revision__ = '$Format:%H$'
#
# from qgis.core import *
# from sextante.parameters.ParameterVector import ParameterVector
# from sextante.core.QGisLayers import QGisLayers
# import os
#
# class CreateMosaicDatastore(GeoServerToolsAlgorithm):
#
# INPUT = "INPUT"
# WORKSPACE = "WORKSPACE"
#
# def processAlgorithm(self, progress):
# self.createCatalog()
# input = self.getParameterValue(self.INPUT)
# workspaceName = self.getParameterValue(self.WORKSPACE)
# connection = {
# 'shp': basepathname + '.shp',
# 'shx': basepathname + '.shx',
# 'dbf': basepathname + '.dbf',
# 'prj': basepathname + '.prj'
# }
#
# workspace = self.catalog.get_workspace(workspaceName)
# self.catalog.create_featurestore(basefilename, connection, workspace)
#
#
# def defineCharacteristics(self):
# self.addcaddBaseParameters()
# self.name = "Import into GeoServer"
# self.group = "GeoServer management tools"
# self.addParameter(ParameterVector(self.INPUT, "Layer to import", ParameterVector.VECTOR_TYPE_ANY))
# self.addParameter(ParameterString(self.WORKSPACE, "Workspace"))
#===============================================================================

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
CreateWorkspace.py
---------------------
Date : October 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
from sextante.servertools.GeoServerToolsAlgorithm import GeoServerToolsAlgorithm
from sextante.outputs.OutputString import OutputString
__author__ = 'Victor Olaya'
__date__ = 'October 2012'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
from qgis.core import *
from PyQt4 import QtGui
from sextante.parameters.ParameterString import ParameterString
class CreateWorkspace(GeoServerToolsAlgorithm):
WORKSPACE = "WORKSPACE"
WORKSPACEURI = "WORKSPACEURI"
def getIcon(self):
return QtGui.QIcon(os.path.dirname(__file__) + "/../images/geoserver.png")
def processAlgorithm(self, progress):
self.createCatalog()
workspaceName = self.getParameterValue(self.WORKSPACE)
workspaceUri = self.getParameterValue(self.WORKSPACEURI)
self.catalog.create_workspace(workspaceName, workspaceUri)
def defineCharacteristics(self):
self.addBaseParameters()
self.name = "Create workspace"
self.group = "GeoServer management tools"
self.addParameter(ParameterString(self.WORKSPACE, "Workspace"))
self.addParameter(ParameterString(self.WORKSPACEURI, "Workspace URI"))
self.addOutput(OutputString(self.WORKSPACE, "Workspace"))

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
DeleteDatastore.py
---------------------
Date : October 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
from sextante.servertools.GeoServerToolsAlgorithm import GeoServerToolsAlgorithm
__author__ = 'Victor Olaya'
__date__ = 'October 2012'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
from qgis.core import *
from PyQt4 import QtGui
from sextante.parameters.ParameterString import ParameterString
class DeleteDatastore(GeoServerToolsAlgorithm):
DATASTORE = "DATASTORE"
WORKSPACE = "WORKSPACE"
def getIcon(self):
return QtGui.QIcon(os.path.dirname(__file__) + "/../images/geoserver.png")
def processAlgorithm(self, progress):
self.createCatalog()
datastoreName = self.getParameterValue(self.DATASTORE)
workspaceName = self.getParameterValue(self.WORKSPACE)
ds = self.catalog.get_store(datastoreName, workspaceName)
self.catalog.delete(ds, recurse=True)
def defineCharacteristics(self):
self.addBaseParameters()
self.name = "Delete datastore"
self.group = "GeoServer management tools"
self.addParameter(ParameterString(self.DATASTORE, "Datastore name"))
self.addParameter(ParameterString(self.WORKSPACE, "Workspace"))

View File

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
DeleteWorkspace.py
---------------------
Date : October 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
from sextante.servertools.GeoServerToolsAlgorithm import GeoServerToolsAlgorithm
__author__ = 'Victor Olaya'
__date__ = 'October 2012'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
from qgis.core import *
from PyQt4 import QtGui
from sextante.parameters.ParameterString import ParameterString
class DeleteWorkspace(GeoServerToolsAlgorithm):
WORKSPACE = "WORKSPACE"
def getIcon(self):
return QtGui.QIcon(os.path.dirname(__file__) + "/../images/geoserver.png")
def processAlgorithm(self, progress):
self.createCatalog()
workspaceName = self.getParameterValue(self.WORKSPACE)
ws = self.catalog.get_workspace(workspaceName)
self.catalog.delete(ws)
def defineCharacteristics(self):
self.addBaseParameters()
self.name = "Delete workspace"
self.group = "GeoServer management tools"
self.addParameter(ParameterString(self.WORKSPACE, "Workspace"))

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
GeoserverToolsAlgorithm.py
---------------------
Date : October 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
from sextante.parameters.ParameterString import ParameterString
from sextante.servertools.geoserver.catalog import Catalog
__author__ = 'Victor Olaya'
__date__ = 'October 2012'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
from PyQt4 import QtGui
from sextante.core.GeoAlgorithm import GeoAlgorithm
class GeoServerToolsAlgorithm(GeoAlgorithm):
URL = "URL"
USER = "USER"
PASSWORD = "PASSWORD"
def getIcon(self):
return QtGui.QIcon(os.path.dirname(__file__) + "/../images/geoserver.png")
def addBaseParameters(self):
self.addParameter(ParameterString(self.URL, "URL", "http://localhost:8080/geoserver/rest"))
self.addParameter(ParameterString(self.USER, "User", "admin"))
self.addParameter(ParameterString(self.PASSWORD, "Password", "geoserver"))
def createCatalog(self):
url = self.getParameterValue(self.URL)
user = self.getParameterValue(self.USER)
password = self.getParameterValue(self.PASSWORD)
self.catalog = Catalog(url, user, password)

View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
GeoServerToolsAlgorithmProvider.py
---------------------
Date : October 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
from sextante.servertools.CreateWorkspace import CreateWorkspace
from sextante.servertools.ImportVectorIntoGeoServer import ImportVectorIntoGeoServer
from sextante.servertools.ImportRasterIntoGeoServer import ImportRasterIntoGeoServer
from sextante.servertools.DeleteWorkspace import DeleteWorkspace
from sextante.servertools.DeleteDatastore import DeleteDatastore
__author__ = 'Victor Olaya'
__date__ = 'October 2012'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from sextante.core.AlgorithmProvider import AlgorithmProvider
from PyQt4 import QtGui
import os
class GeoServerToolsAlgorithmProvider(AlgorithmProvider):
def __init__(self):
AlgorithmProvider.__init__(self)
self.alglist = [ImportVectorIntoGeoServer(), ImportRasterIntoGeoServer(),
CreateWorkspace(), DeleteWorkspace(), DeleteDatastore()]#, CreateMosaicGeoserver(), StyleGeoserverLayer(), TruncateSeedGWC()]
def initializeSettings(self):
AlgorithmProvider.initializeSettings(self)
def unload(self):
AlgorithmProvider.unload(self)
def getName(self):
return "geoserver"
def getDescription(self):
return "Geoserver management tools"
def getIcon(self):
return QtGui.QIcon(os.path.dirname(__file__) + "/../images/geoserver.png")
def _loadAlgorithms(self):
self.algs = self.alglist
def supportsNonFileBasedOutput(self):
return True

View File

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
ImportVectorIntoGeoServer.py
---------------------
Date : October 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
from sextante.core.LayerExporter import LayerExporter
from sextante.parameters.ParameterString import ParameterString
from sextante.servertools.GeoServerToolsAlgorithm import GeoServerToolsAlgorithm
from sextante.parameters.ParameterRaster import ParameterRaster
__author__ = 'Victor Olaya'
__date__ = 'October 2012'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from qgis.core import *
class ImportRasterIntoGeoServer(GeoServerToolsAlgorithm):
INPUT = "INPUT"
WORKSPACE = "WORKSPACE"
NAME = "NAME"
def exportRasterLayer(self, inputFilename):
return inputFilename
def processAlgorithm(self, progress):
self.createCatalog()
inputFilename = self.getParameterValue(self.INPUT)
name = self.getParameterValue(self.NAME)
workspaceName = self.getParameterValue(self.WORKSPACE)
filename = self.exportRasterLayer(inputFilename)
workspace = self.catalog.get_workspace(workspaceName)
ds = self.catalog.create_coveragestore2(name, workspace)
ds.data_url = "file:" + filename;
self.catalog.save(ds)
def defineCharacteristics(self):
self.addBaseParameters()
self.name = "Import raster into GeoServer"
self.group = "GeoServer management tools"
self.addParameter(ParameterRaster(self.INPUT, "Layer to import"))
self.addParameter(ParameterString(self.WORKSPACE, "Workspace"))
self.addParameter(ParameterString(self.NAME, "Store name"))

View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
ImportVectorIntoGeoServer.py
---------------------
Date : October 2012
Copyright : (C) 2012 by Victor Olaya
Email : volayaf at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
from sextante.core.LayerExporter import LayerExporter
from sextante.parameters.ParameterString import ParameterString
from sextante.servertools.GeoServerToolsAlgorithm import GeoServerToolsAlgorithm
__author__ = 'Victor Olaya'
__date__ = 'October 2012'
__copyright__ = '(C) 2012, Victor Olaya'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from qgis.core import *
from sextante.parameters.ParameterVector import ParameterVector
from sextante.core.QGisLayers import QGisLayers
import os
class ImportVectorIntoGeoServer(GeoServerToolsAlgorithm):
INPUT = "INPUT"
WORKSPACE = "WORKSPACE"
def processAlgorithm(self, progress):
self.createCatalog()
inputFilename = self.getParameterValue(self.INPUT)
layer = QGisLayers.getObjectFromUri(inputFilename)
workspaceName = self.getParameterValue(self.WORKSPACE)
filename = LayerExporter.exportVectorLayer(layer)
basefilename = os.path.basename(filename)
basepathname = os.path.dirname(filename) + os.sep + basefilename[:basefilename.find('.')]
connection = {
'shp': basepathname + '.shp',
'shx': basepathname + '.shx',
'dbf': basepathname + '.dbf',
'prj': basepathname + '.prj'
}
workspace = self.catalog.get_workspace(workspaceName)
self.catalog.create_featurestore(basefilename, connection, workspace)
def defineCharacteristics(self):
self.addBaseParameters()
self.name = "Import vector into GeoServer"
self.group = "GeoServer management tools"
self.addParameter(ParameterVector(self.INPUT, "Layer to import", ParameterVector.VECTOR_TYPE_ANY))
self.addParameter(ParameterString(self.WORKSPACE, "Workspace"))

View File

@ -0,0 +1,504 @@
from datetime import datetime, timedelta
import logging
from sextante.servertools.geoserver.layer import Layer
from sextante.servertools.geoserver.store import coveragestore_from_index, datastore_from_index, \
UnsavedDataStore, UnsavedCoverageStore
from sextante.servertools.geoserver.style import Style
from sextante.servertools.geoserver.support import prepare_upload_bundle, url
from sextante.servertools.geoserver.layergroup import LayerGroup, UnsavedLayerGroup
from sextante.servertools.geoserver.workspace import workspace_from_index, Workspace
from os import unlink
from xml.etree.ElementTree import XML
from xml.parsers.expat import ExpatError
from urlparse import urlparse
from sextante.servertools import httplib2
logger = logging.getLogger("gsconfig.catalog")
class UploadError(Exception):
pass
class ConflictingDataError(Exception):
pass
class AmbiguousRequestError(Exception):
pass
class FailedRequestError(Exception):
pass
def _name(named):
"""Get the name out of an object. This varies based on the type of the input:
* the "name" of a string is itself
* the "name" of None is itself
* the "name" of an object with a property named name is that property -
as long as it's a string
* otherwise, we raise a ValueError
"""
if isinstance(named, basestring) or named is None:
return named
elif hasattr(named, 'name') and isinstance(named.name, basestring):
return named.name
else:
raise ValueError("Can't interpret %s as a name or a configuration object" % named)
class Catalog(object):
"""
The GeoServer catalog represents all of the information in the GeoServer
configuration. This includes:
- Stores of geospatial data
- Resources, or individual coherent datasets within stores
- Styles for resources
- Layers, which combine styles with resources to create a visible map layer
- LayerGroups, which alias one or more layers for convenience
- Workspaces, which provide logical grouping of Stores
- Maps, which provide a set of OWS services with a subset of the server's
Layers
- Namespaces, which provide unique identifiers for resources
"""
def __init__(self, service_url, username="admin", password="geoserver", disable_ssl_certificate_validation=False):
self.service_url = service_url
if self.service_url.endswith("/"):
self.service_url = self.service_url.strip("/")
self.http = httplib2.Http(
disable_ssl_certificate_validation=disable_ssl_certificate_validation)
self.username = username
self.password = password
self.http.add_credentials(self.username, self.password)
netloc = urlparse(service_url).netloc
self.http.authorizations.append(
httplib2.BasicAuthentication(
(username, password),
netloc,
service_url,
{},
None,
None,
self.http
))
self._cache = dict()
def delete(self, config_object, purge=False, recurse=False):
"""
send a delete request
XXX [more here]
"""
rest_url = config_object.href
#params aren't supported fully in httplib2 yet, so:
params = []
# purge deletes the SLD from disk when a style is deleted
if purge:
params.append("purge=true")
# recurse deletes the resource when a layer is deleted.
if recurse:
params.append("recurse=true")
if params:
rest_url = rest_url + "?" + "&".join(params)
headers = {
"Content-type": "application/xml",
"Accept": "application/xml"
}
response, content = self.http.request(rest_url, "DELETE", headers=headers)
self._cache.clear()
if response.status == 200:
return (response, content)
else:
raise FailedRequestError("Tried to make a DELETE request to %s but got a %d status code: \n%s" % (rest_url, response.status, content))
def get_xml(self, rest_url):
logger.debug("GET %s", rest_url)
cached_response = self._cache.get(rest_url)
def is_valid(cached_response):
return cached_response is not None and datetime.now() - cached_response[0] < timedelta(seconds=5)
def parse_or_raise(xml):
try:
return XML(xml)
except (ExpatError, SyntaxError), e:
msg = "GeoServer gave non-XML response for [GET %s]: %s"
msg = msg % (rest_url, xml)
raise Exception(msg, e)
if is_valid(cached_response):
raw_text = cached_response[1]
return parse_or_raise(raw_text)
else:
response, content = self.http.request(rest_url)
if response.status == 200:
self._cache[rest_url] = (datetime.now(), content)
return parse_or_raise(content)
else:
raise FailedRequestError("Tried to make a GET request to %s but got a %d status code: \n%s" % (url, response.status, content))
def reload(self):
reload_url = url(self.service_url, ['reload'])
response = self.http.request(reload_url, "POST")
self._cache.clear()
return response
def save(self, obj):
"""
saves an object to the REST service
gets the object's REST location and the XML from the object,
then POSTS the request.
"""
rest_url = obj.href
message = obj.message()
headers = {
"Content-type": "application/xml",
"Accept": "application/xml"
}
logger.debug("%s %s", obj.save_method, obj.href)
response = self.http.request(rest_url, obj.save_method, message, headers)
headers, body = response
self._cache.clear()
if 400 <= int(headers['status']) < 600:
raise FailedRequestError("Error code (%s) from GeoServer: %s" %
(headers['status'], body))
return response
def get_store(self, name, workspace=None):
#stores = [s for s in self.get_stores(workspace) if s.name == name]
if workspace is None:
store = None
for ws in self.get_workspaces():
found = None
try:
found = self.get_store(name, ws)
except:
# don't expect every workspace to contain the named store
pass
if found:
if store:
raise AmbiguousRequestError("Multiple stores found named: " + name)
else:
store = found
if not store:
raise FailedRequestError("No store found named: " + name)
return store
else: # workspace is not None
if isinstance(workspace, basestring):
workspace = self.get_workspace(workspace)
if workspace is None:
return None
logger.debug("datastore url is [%s]", workspace.datastore_url )
ds_list = self.get_xml(workspace.datastore_url)
cs_list = self.get_xml(workspace.coveragestore_url)
datastores = [n for n in ds_list.findall("dataStore") if n.find("name").text == name]
coveragestores = [n for n in cs_list.findall("coverageStore") if n.find("name").text == name]
ds_len, cs_len = len(datastores), len(coveragestores)
if ds_len == 1 and cs_len == 0:
return datastore_from_index(self, workspace, datastores[0])
elif ds_len == 0 and cs_len == 1:
return coveragestore_from_index(self, workspace, coveragestores[0])
elif ds_len == 0 and cs_len == 0:
raise FailedRequestError("No store found in " + str(workspace) + " named: " + name)
else:
raise AmbiguousRequestError(str(workspace) + " and name: " + name + " do not uniquely identify a layer")
def get_stores(self, workspace=None):
if workspace is not None:
if isinstance(workspace, basestring):
workspace = self.get_workspace(workspace)
ds_list = self.get_xml(workspace.datastore_url)
cs_list = self.get_xml(workspace.coveragestore_url)
datastores = [datastore_from_index(self, workspace, n) for n in ds_list.findall("dataStore")]
coveragestores = [coveragestore_from_index(self, workspace, n) for n in cs_list.findall("coverageStore")]
return datastores + coveragestores
else:
stores = []
for ws in self.get_workspaces():
a = self.get_stores(ws)
stores.extend(a)
return stores
def create_datastore(self, name, workspace=None):
if isinstance(workspace, basestring):
workspace = self.get_workspace(workspace)
elif workspace is None:
workspace = self.get_default_workspace()
return UnsavedDataStore(self, name, workspace)
def create_coveragestore2(self, name, workspace = None):
"""
Hm we already named the method that creates a coverage *resource*
create_coveragestore... time for an API break?
"""
if isinstance(workspace, basestring):
workspace = self.get_workspace(workspace)
elif workspace is None:
workspace = self.get_default_workspace()
return UnsavedCoverageStore(self, name, workspace)
def add_data_to_store(self, store, name, data, workspace=None, overwrite = False, charset = None):
if isinstance(store, basestring):
store = self.get_store(store, workspace=workspace)
if workspace is not None:
workspace = _name(workspace)
assert store.workspace.name == workspace, "Specified store (%s) is not in specified workspace (%s)!" % (store, workspace)
else:
workspace = store.workspace.name
store = store.name
if isinstance(data, dict):
bundle = prepare_upload_bundle(name, data)
else:
bundle = data
params = dict()
if overwrite:
params["update"] = "overwrite"
if charset is not None:
params["charset"] = charset
message = open(bundle)
headers = { 'Content-Type': 'application/zip', 'Accept': 'application/xml' }
upload_url = url(self.service_url,
["workspaces", workspace, "datastores", store, "file.shp"], params)
try:
headers, response = self.http.request(upload_url, "PUT", message, headers)
self._cache.clear()
if headers.status != 201:
raise UploadError(response)
finally:
unlink(bundle)
def create_featurestore(self, name, data, workspace=None, overwrite=False, charset=None):
if not overwrite:
try:
store = self.get_store(name, workspace)
msg = "There is already a store named " + name
if workspace:
msg += " in " + str(workspace)
raise ConflictingDataError(msg)
except FailedRequestError:
# we don't really expect that every layer name will be taken
pass
if workspace is None:
workspace = self.get_default_workspace()
workspace = _name(workspace)
params = dict()
if charset is not None:
params['charset'] = charset
ds_url = url(self.service_url,
["workspaces", workspace, "datastores", name, "file.shp"], params)
# PUT /workspaces/<ws>/datastores/<ds>/file.shp
headers = {
"Content-type": "application/zip",
"Accept": "application/xml"
}
if isinstance(data,dict):
logger.debug('Data is NOT a zipfile')
archive = prepare_upload_bundle(name, data)
else:
logger.debug('Data is a zipfile')
archive = data
message = open(archive)
try:
headers, response = self.http.request(ds_url, "PUT", message, headers)
self._cache.clear()
if headers.status != 201:
raise UploadError(response)
finally:
unlink(archive)
def create_coveragestore(self, name, data, workspace=None, overwrite=False):
if not overwrite:
try:
store = self.get_store(name, workspace)
msg = "There is already a store named " + name
if workspace:
msg += " in " + str(workspace)
raise ConflictingDataError(msg)
except FailedRequestError:
# we don't really expect that every layer name will be taken
pass
if workspace is None:
workspace = self.get_default_workspace()
headers = {
"Content-type": "image/tiff",
"Accept": "application/xml"
}
archive = None
ext = "geotiff"
if isinstance(data, dict):
archive = prepare_upload_bundle(name, data)
message = open(archive)
if "tfw" in data:
headers['Content-type'] = 'application/archive'
ext = "worldimage"
elif isinstance(data, basestring):
message = open(data)
else:
message = data
cs_url = url(self.service_url,
["workspaces", workspace.name, "coveragestores", name, "file." + ext])
try:
headers, response = self.http.request(cs_url, "PUT", message, headers)
self._cache.clear()
if headers.status != 201:
raise UploadError(response)
finally:
if archive is not None:
unlink(archive)
def get_resource(self, name, store=None, workspace=None):
if store is not None:
candidates = [s for s in self.get_resources(store) if s.name == name]
if len(candidates) == 0:
return None
elif len(candidates) > 1:
raise AmbiguousRequestError
else:
return candidates[0]
if workspace is not None:
for store in self.get_stores(workspace):
resource = self.get_resource(name, store)
if resource is not None:
return resource
return None
for ws in self.get_workspaces():
resource = self.get_resource(name, workspace=ws)
if resource is not None:
return resource
return None
def get_resources(self, store=None, workspace=None):
if isinstance(workspace, basestring):
workspace = self.get_workspace(workspace)
if isinstance(store, basestring):
store = self.get_store(store, workspace)
if store is not None:
return store.get_resources()
if workspace is not None:
resources = []
for store in self.get_stores(workspace):
resources.extend(self.get_resources(store))
return resources
resources = []
for ws in self.get_workspaces():
resources.extend(self.get_resources(workspace=ws))
return resources
def get_layer(self, name):
try:
lyr = Layer(self, name)
lyr.fetch()
return lyr
except FailedRequestError:
return None
def get_layers(self, resource=None):
if isinstance(resource, basestring):
resource = self.get_resource(resource)
layers_url = url(self.service_url, ["layers.xml"])
description = self.get_xml(layers_url)
lyrs = [Layer(self, l.find("name").text) for l in description.findall("layer")]
if resource is not None:
lyrs = [l for l in lyrs if l.resource.href == resource.href]
# TODO: Filter by style
return lyrs
def get_layergroup(self, name=None):
try:
group_url = url(self.service_url, ["layergroups", name + ".xml"])
group = self.get_xml(group_url)
return LayerGroup(self, group.find("name").text)
except FailedRequestError:
return None
def get_layergroups(self):
groups = self.get_xml("%s/layergroups.xml" % self.service_url)
return [LayerGroup(self, g.find("name").text) for g in groups.findall("layerGroup")]
def create_layergroup(self, name, layers = (), styles = (), bounds = None):
if any(g.name == name for g in self.get_layergroups()):
raise ConflictingDataError("LayerGroup named %s already exists!" % name)
else:
return UnsavedLayerGroup(self, name, layers, styles, bounds)
def get_style(self, name):
try:
style_url = url(self.service_url, ["styles", name + ".xml"])
dom = self.get_xml(style_url)
return Style(self, dom.find("name").text)
except FailedRequestError:
return None
def get_styles(self):
styles_url = url(self.service_url, ["styles.xml"])
description = self.get_xml(styles_url)
return [Style(self, s.find('name').text) for s in description.findall("style")]
def create_style(self, name, data, overwrite = False):
if overwrite == False and self.get_style(name) is not None:
raise ConflictingDataError("There is already a style named %s" % name)
headers = {
"Content-type": "application/vnd.ogc.sld+xml",
"Accept": "application/xml"
}
if overwrite:
style_url = url(self.service_url, ["styles", name + ".sld"])
headers, response = self.http.request(style_url, "PUT", data, headers)
else:
style_url = url(self.service_url, ["styles"], dict(name=name))
headers, response = self.http.request(style_url, "POST", data, headers)
self._cache.clear()
if headers.status < 200 or headers.status > 299: raise UploadError(response)
def create_workspace(self, name, uri):
xml = ("<namespace>"
"<prefix>{name}</prefix>"
"<uri>{uri}</uri>"
"</namespace>").format(name=name, uri=uri)
headers = { "Content-Type": "application/xml" }
workspace_url = self.service_url + "/namespaces/"
headers, response = self.http.request(workspace_url, "POST", xml, headers)
assert 200 <= headers.status < 300, "Tried to create workspace but got " + str(headers.status) + ": " + response
self._cache.clear()
return self.get_workspace(name)
def get_workspaces(self):
description = self.get_xml("%s/workspaces.xml" % self.service_url)
return [workspace_from_index(self, node) for node in description.findall("workspace")]
def get_workspace(self, name):
candidates = [w for w in self.get_workspaces() if w.name == name]
if len(candidates) == 0:
return None
elif len(candidates) > 1:
raise AmbiguousRequestError()
else:
return candidates[0]
def get_default_workspace(self):
return Workspace(self, "default")
def set_default_workspace(self):
raise NotImplementedError()

View File

@ -0,0 +1,132 @@
from sextante.servertools.geoserver.support import ResourceInfo, xml_property, write_bool, url
from sextante.servertools.geoserver.style import Style
class _attribution(object):
def __init__(self, title, width, height):
self.title = title
self.width = width
self.height = height
def _read_attribution(node):
title = node.find("title")
width = node.find("logoWidth")
height = node.find("logoHeight")
if title is not None:
title = title.text
if width is not None:
width = width.text
if height is not None:
height = height.text
return _attribution(title, width, height)
def _write_attribution(builder, attr):
builder.start("attribution", dict())
if attr.title is not None:
builder.start("title", dict())
builder.data(attr.title)
builder.end("title")
if attr.width is not None:
builder.start("logoWidth", dict())
builder.data(attr.width)
builder.end("logoWidth")
if attr.height is not None:
builder.start("logoHeight", dict())
builder.data(attr.height)
builder.end("logoHeight")
builder.end("attribution")
def _write_default_style(builder, name):
builder.start("defaultStyle", dict())
if name is not None:
builder.start("name", dict())
builder.data(name)
builder.end("name")
builder.end("defaultStyle")
def _write_alternate_styles(builder, styles):
builder.start("styles", dict())
for s in styles:
builder.start("style", dict())
builder.start("name", dict())
builder.data(s.name)
builder.end("name")
builder.end("style")
builder.end("styles")
class Layer(ResourceInfo):
def __init__(self, catalog, name):
super(Layer, self).__init__()
self.catalog = catalog
self.name = name
resource_type = "layer"
save_method = "PUT"
@property
def href(self):
return url(self.catalog.service_url, ["layers", self.name + ".xml"])
@property
def resource(self):
if self.dom is None:
self.fetch()
name = self.dom.find("resource/name").text
return self.catalog.get_resource(name)
def _get_default_style(self):
if 'default_style' in self.dirty:
return self.dirty['default_style']
if self.dom is None:
self.fetch()
name = self.dom.find("defaultStyle/name")
# aborted data uploads can result in no default style
if name is not None:
return self.catalog.get_style(name.text)
else:
return None
def _set_default_style(self, style):
if isinstance(style, Style):
style = style.name
self.dirty["default_style"] = style
def _get_alternate_styles(self):
if "alternate_styles" in self.dirty:
return self.dirty["alternate_styles"]
if self.dom is None:
self.fetch()
styles = self.dom.findall("styles/style/name")
return [Style(self.catalog, s.text) for s in styles]
def _set_alternate_styles(self, styles):
self.dirty["alternate_styles"] = styles
default_style = property(_get_default_style, _set_default_style)
styles = property(_get_alternate_styles, _set_alternate_styles)
attribution_object = xml_property("attribution", _read_attribution)
enabled = xml_property("enabled", lambda x: x.text == "true")
def _get_attr_text(self):
return self.attribution_object.title
def _set_attr_text(self, text):
self.dirty["attribution"] = _attribution(
text,
self.attribution_object.width,
self.attribution_object.height
)
assert self.attribution_object.title == text
attribution = property(_get_attr_text, _set_attr_text)
writers = dict(
attribution = _write_attribution,
enabled = write_bool("enabled"),
default_style = _write_default_style,
alternate_styles = _write_alternate_styles
)

View File

@ -0,0 +1,84 @@
from sextante.servertools.geoserver.support import ResourceInfo, bbox, write_bbox, \
write_string, xml_property, url
def _maybe_text(n):
if n is None:
return None
else:
return n.text
def _layer_list(node):
if node is not None:
return [_maybe_text(n.find("name")) for n in node.findall("layer")]
def _style_list(node):
if node is not None:
return [_maybe_text(n.find("name")) for n in node.findall("style")]
def _write_layers(builder, layers):
builder.start("layers", dict())
for l in layers:
builder.start("layer", dict())
if l is not None:
builder.start("name", dict())
builder.data(l)
builder.end("name")
builder.end("layer")
builder.end("layers")
def _write_styles(builder, styles):
builder.start("styles", dict())
for s in styles:
builder.start("style", dict())
if s is not None:
builder.start("name", dict())
builder.data(s)
builder.end("name")
builder.end("style")
builder.end("styles")
class LayerGroup(ResourceInfo):
"""
Represents a layer group in geoserver
"""
resource_type = "layerGroup"
save_method = "PUT"
def __init__(self, catalog, name):
super(LayerGroup, self).__init__()
assert isinstance(name, basestring)
self.catalog = catalog
self.name = name
@property
def href(self):
return url(self.catalog.service_url, ["layergroups", self.name + ".xml"])
styles = xml_property("styles", _style_list)
layers = xml_property("layers", _layer_list)
bounds = xml_property("bounds", bbox)
writers = dict(
name = write_string("name"),
styles = _write_styles,
layers = _write_layers,
bounds = write_bbox("bounds")
)
def __str__(self):
return "<LayerGroup %s>" % self.name
__repr__ = __str__
class UnsavedLayerGroup(LayerGroup):
save_method = "POST"
def __init__(self, catalog, name, layers, styles, bounds):
super(UnsavedLayerGroup, self).__init__(catalog, name)
self.dirty.update(name = name, layers = layers, styles = styles, bounds = bounds)
@property
def href(self):
return "%s/layergroups?name=%s" % (self.catalog.service_url, self.name)

View File

@ -0,0 +1,176 @@
from sextante.servertools.geoserver.support import ResourceInfo, xml_property, write_string, bbox, \
write_bbox, string_list, write_string_list, attribute_list, write_bool, url
def md_link(node):
"""Extract a metadata link tuple from an xml node"""
mimetype = node.find("type")
mdtype = node.find("metadataType")
content = node.find("content")
if None in [mimetype, mdtype, content]:
return None
else:
return (mimetype.text, mdtype.text, content.text)
def metadata_link_list(node):
if node is not None:
return [md_link(n) for n in node.findall("metadataLink")]
def write_metadata_link_list(name):
def write(builder, md_links):
builder.start(name, dict())
for (mime, md_type, content_url) in md_links:
builder.start("metadataLink", dict())
builder.start("type", dict())
builder.data(mime)
builder.end("type")
builder.start("metadataType", dict())
builder.data(md_type)
builder.end("metadataType")
builder.start("content", dict())
builder.data(content_url)
builder.end("content")
builder.end("metadataLink")
builder.end("metadataLinks")
return write
def featuretype_from_index(catalog, workspace, store, node):
name = node.find("name")
return FeatureType(catalog, workspace, store, name.text)
def coverage_from_index(catalog, workspace, store, node):
name = node.find("name")
return Coverage(catalog, workspace, store, name.text)
class FeatureType(ResourceInfo):
resource_type = "featureType"
save_method = "PUT"
def __init__(self, catalog, workspace, store, name):
super(FeatureType, self).__init__()
assert isinstance(store, ResourceInfo)
assert isinstance(name, basestring)
self.catalog = catalog
self.workspace = workspace
self.store = store
self.name = name
@property
def href(self):
return url(self.catalog.service_url,
["workspaces", self.workspace.name,
"datastores", self.store.name,
"featuretypes", self.name + ".xml"])
title = xml_property("title")
abstract = xml_property("abstract")
enabled = xml_property("enabled")
native_bbox = xml_property("nativeBoundingBox", bbox)
latlon_bbox = xml_property("latLonBoundingBox", bbox)
projection = xml_property("srs")
projection_policy = xml_property("projectionPolicy")
keywords = xml_property("keywords", string_list)
attributes = xml_property("attributes", attribute_list)
metadata_links = xml_property("metadataLinks", metadata_link_list)
writers = dict(
title = write_string("title"),
abstract = write_string("abstract"),
enabled = write_bool("enabled"),
nativeBoundingBox = write_bbox("nativeBoundingBox"),
latLonBoundingBox = write_bbox("latLonBoundingBox"),
srs = write_string("srs"),
projectionPolicy = write_string("projectionPolicy"),
keywords = write_string_list("keywords"),
metadataLinks = write_metadata_link_list("metadataLinks")
)
class CoverageDimension(object):
def __init__(self, name, description, dimension_range):
self.name = name
self.description = description
self.dimension_range = dimension_range
def coverage_dimension(node):
name = node.find("name")
name = name.text if name is not None else None
description = node.find("description")
description = description.text if description is not None else None
range_min = node.find("range/min")
range_max = node.find("range/max")
dimension_range = None
if None not in [min, max]:
dimension_range = float(range_min.text), float(range_max.text)
if None not in [name, description]:
return CoverageDimension(name, description, dimension_range)
else:
return None # should we bomb out more spectacularly here?
def coverage_dimension_xml(builder, dimension):
builder.start("coverageDimension", dict())
builder.start("name", dict())
builder.data(dimension.name)
builder.end("name")
builder.start("description", dict())
builder.data(dimension.description)
builder.end("description")
if dimension.range is not None:
builder.start("range", dict())
builder.start("min", dict())
builder.data(str(dimension.range[0]))
builder.end("min")
builder.start("max", dict())
builder.data(str(dimension.range[1]))
builder.end("max")
builder.end("range")
builder.end("coverageDimension")
class Coverage(ResourceInfo):
def __init__(self, catalog, workspace, store, name):
super(Coverage, self).__init__()
self.catalog = catalog
self.workspace = workspace
self.store = store
self.name = name
@property
def href(self):
return url(self.catalog.service_url,
["workspaces", self.workspace.name,
"coveragestores", self.store.name,
"coverages", self.name + ".xml"])
resource_type = "coverage"
save_method = "PUT"
title = xml_property("title")
abstract = xml_property("abstract")
enabled = xml_property("enabled")
native_bbox = xml_property("nativeBoundingBox", bbox)
latlon_bbox = xml_property("latLonBoundingBox", bbox)
projection = xml_property("srs")
projection_policy = xml_property("projectionPolicy")
keywords = xml_property("keywords", string_list)
request_srs_list = xml_property("requestSRS", string_list)
response_srs_list = xml_property("responseSRS", string_list)
supported_formats = xml_property("supportedFormats", string_list)
metadata_links = xml_property("metadataLinks", metadata_link_list)
writers = dict(
title = write_string("title"),
abstract = write_string("abstract"),
enabled = write_bool("enabled"),
nativeBoundingBox = write_bbox("nativeBoundingBox"),
latLonBoundingBox = write_bbox("latLonBoundingBox"),
srs = write_string("srs"),
projection_policy = write_string("projectionPolicy"),
keywords = write_string_list("keywords"),
metadataLinks = write_metadata_link_list("metadataLinks"),
requestSRS = write_string_list("requestSRS"),
responseSRS = write_string_list("responseSRS"),
supportedFormats = write_string_list("supportedFormats")
)

View File

@ -0,0 +1,117 @@
import sextante.servertools.geoserver.workspace as ws
from sextante.servertools.geoserver.resource import featuretype_from_index, coverage_from_index
from sextante.servertools.geoserver.support import ResourceInfo, xml_property, key_value_pairs, \
write_bool, write_dict, write_string, url
def datastore_from_index(catalog, workspace, node):
name = node.find("name")
return DataStore(catalog, workspace, name.text)
def coveragestore_from_index(catalog, workspace, node):
name = node.find("name")
return CoverageStore(catalog, workspace, name.text)
class DataStore(ResourceInfo):
resource_type = "dataStore"
save_method = "PUT"
def __init__(self, catalog, workspace, name):
super(DataStore, self).__init__()
assert isinstance(workspace, ws.Workspace)
assert isinstance(name, basestring)
self.catalog = catalog
self.workspace = workspace
self.name = name
@property
def href(self):
return url(self.catalog.service_url,
["workspaces", self.workspace.name, "datastores", self.name + ".xml"])
enabled = xml_property("enabled", lambda x: x.text == "true")
name = xml_property("name")
connection_parameters = xml_property("connectionParameters", key_value_pairs)
writers = dict(enabled = write_bool("enabled"),
name = write_string("name"),
connectionParameters = write_dict("connectionParameters"))
def get_resources(self):
res_url = url(self.catalog.service_url,
["workspaces", self.workspace.name, "datastores", self.name, "featuretypes.xml"])
xml = self.catalog.get_xml(res_url)
def ft_from_node(node):
return featuretype_from_index(self.catalog, self.workspace, self, node)
return [ft_from_node(node) for node in xml.findall("featureType")]
class UnsavedDataStore(DataStore):
save_method = "POST"
def __init__(self, catalog, name, workspace):
super(UnsavedDataStore, self).__init__(catalog, workspace, name)
self.dirty.update(dict(
name=name, enabled=True, connectionParameters=dict()))
@property
def href(self):
path = [ "workspaces",
self.workspace.name, "datastores"]
query = dict(name=self.name)
return url(self.catalog.service_url, path, query)
class CoverageStore(ResourceInfo):
resource_type = 'coverageStore'
save_method = "PUT"
def __init__(self, catalog, workspace, name):
super(CoverageStore, self).__init__()
assert isinstance(workspace, ws.Workspace)
assert isinstance(name, basestring)
self.catalog = catalog
self.workspace = workspace
self.name = name
@property
def href(self):
return url(self.catalog.service_url,
["workspaces", self.workspace.name, "coveragestores", self.name + ".xml"])
enabled = xml_property("enabled", lambda x: x.text == "true")
name = xml_property("name")
url = xml_property("url")
type = xml_property("type")
writers = dict(enabled = write_bool("enabled"),
name = write_string("name"),
url = write_string("url"),
type = write_string("type"))
def get_resources(self):
res_url = url(self.catalog.service_url,
["workspaces", self.workspace.name, "coveragestores", self.name, "coverages.xml"])
xml = self.catalog.get_xml(res_url)
def cov_from_node(node):
return coverage_from_index(self.catalog, self.workspace, self, node)
return [cov_from_node(node) for node in xml.findall("coverage")]
class UnsavedCoverageStore(CoverageStore):
save_method = "POST"
def __init__(self, catalog, name, workspace):
super(UnsavedCoverageStore, self).__init__(catalog, workspace, name)
self.dirty.update(name=name, enabled = True, type="GeoTIFF",
url = "file:data/")
@property
def href(self):
return url(self.catalog.service_url,
["workspaces", self.workspace.name, "coveragestores"], dict(name=self.name))

View File

@ -0,0 +1,46 @@
from sextante.servertools.geoserver.support import ResourceInfo, url, xml_property
class Style(ResourceInfo):
def __init__(self, catalog, name):
super(Style, self).__init__()
assert isinstance(name, basestring)
self.catalog = catalog
self.name = name
self._sld_dom = None
@property
def href(self):
return url(self.catalog.service_url, ["styles", self.name + ".xml"])
def body_href(self):
return url(self.catalog.service_url, ["styles", self.name + ".sld"])
filename = xml_property("filename")
def _get_sld_dom(self):
if self._sld_dom is None:
self._sld_dom = self.catalog.get_xml(self.body_href())
return self._sld_dom
@property
def sld_title(self):
user_style = self._get_sld_dom().find("{http://www.opengis.net/sld}NamedLayer/{http://www.opengis.net/sld}UserStyle")
title_node = user_style.find("{http://www.opengis.net/sld}Title")
return title_node.text if title_node is not None else None
@property
def sld_name(self):
user_style = self._get_sld_dom().find("{http://www.opengis.net/sld}NamedLayer/{http://www.opengis.net/sld}UserStyle")
name_node = user_style.find("{http://www.opengis.net/sld}Name")
return name_node.text if name_node is not None else None
@property
def sld_body(self):
content = self.catalog.http.request(self.body_href())[1]
return content
def update_body(self, body):
headers = { "Content-Type": "application/vnd.ogc.sld+xml" }
self.catalog.http.request(
self.body_href(), "PUT", body, headers)

View File

@ -0,0 +1,218 @@
import logging
from xml.etree.ElementTree import TreeBuilder, tostring
import urllib
import urlparse
from zipfile import ZipFile
from sextante.core.SextanteUtils import SextanteUtils
logger = logging.getLogger("gsconfig.support")
FORCE_DECLARED = "FORCE_DECLARED"
## The projection handling policy for layers that should use coordinates
## directly while reporting the configured projection to clients. This should be
## used when projection information is missing from the underlying datastore.
FORCE_NATIVE = "FORCE_NATIVE"
## The projection handling policy for layers that should use the projection
## information from the underlying storage mechanism directly, and ignore the
## projection setting.
REPROJECT = "REPROJECT"
## The projection handling policy for layers that should use the projection
## information from the underlying storage mechanism to reproject to the
## configured projection.
def url(base, seg, query=None):
"""
Create a URL from a list of path segments and an optional dict of query
parameters.
"""
seg = (urllib.quote(s.strip('/')) for s in seg)
if query is None or len(query) == 0:
query_string = ''
else:
query_string = "?" + urllib.urlencode(query)
path = '/'.join(seg) + query_string
adjusted_base = base.rstrip('/') + '/'
return urlparse.urljoin(adjusted_base, path)
def xml_property(path, converter = lambda x: x.text):
def getter(self):
if path in self.dirty:
return self.dirty[path]
else:
if self.dom is None:
self.fetch()
node = self.dom.find(path)
return converter(self.dom.find(path)) if node is not None else None
def setter(self, value):
self.dirty[path] = value
def delete(self):
self.dirty[path] = None
return property(getter, setter, delete)
def bbox(node):
if node is not None:
minx = node.find("minx")
maxx = node.find("maxx")
miny = node.find("miny")
maxy = node.find("maxy")
crs = node.find("crs")
crs = crs.text if crs is not None else None
if (None not in [minx, maxx, miny, maxy]):
return (minx.text, maxx.text, miny.text, maxy.text, crs)
else:
return None
else:
return None
def string_list(node):
if node is not None:
return [n.text for n in node.findall("string")]
def attribute_list(node):
if node is not None:
return [n.text for n in node.findall("attribute/name")]
def key_value_pairs(node):
if node is not None:
return dict((entry.attrib['key'], entry.text) for entry in node.findall("entry"))
def write_string(name):
def write(builder, value):
builder.start(name, dict())
if (value is not None):
builder.data(value)
builder.end(name)
return write
def write_bool(name):
def write(builder, b):
builder.start(name, dict())
builder.data("true" if b else "false")
builder.end(name)
return write
def write_bbox(name):
def write(builder, b):
builder.start(name, dict())
bbox_xml(builder, b)
builder.end(name)
return write
def write_string_list(name):
def write(builder, words):
builder.start(name, dict())
for w in words:
builder.start("string", dict())
builder.data(w)
builder.end("string")
builder.end(name)
return write
def write_dict(name):
def write(builder, pairs):
builder.start(name, dict())
for k, v in pairs.iteritems():
builder.start("entry", dict(key=k))
builder.data(v)
builder.end("entry")
builder.end(name)
return write
class ResourceInfo(object):
def __init__(self):
self.dom = None
self.dirty = dict()
def fetch(self):
self.dom = self.catalog.get_xml(self.href)
def clear(self):
self.dirty = dict()
def refresh(self):
self.clear()
self.fetch()
def serialize(self, builder):
# GeoServer will disable the resource if we omit the <enabled> tag,
# so force it into the dirty dict before writing
if hasattr(self, "enabled"):
self.dirty['enabled'] = self.enabled
for k, writer in self.writers.items():
if k in self.dirty:
writer(builder, self.dirty[k])
def message(self):
builder = TreeBuilder()
builder.start(self.resource_type, dict())
self.serialize(builder)
builder.end(self.resource_type)
msg = tostring(builder.close())
return msg
def prepare_upload_bundle(name, data):
"""GeoServer's REST API uses ZIP archives as containers for file formats such
as Shapefile and WorldImage which include several 'boxcar' files alongside
the main data. In such archives, GeoServer assumes that all of the relevant
files will have the same base name and appropriate extensions, and live in
the root of the ZIP archive. This method produces a zip file that matches
these expectations, based on a basename, and a dict of extensions to paths or
file-like objects. The client code is responsible for deleting the zip
archive when it's done."""
#we ut the zip file in the sextante temp dir, so it is deleted at the end.
f = SextanteUtils.getTempFilename('zip')
zip_file = ZipFile(f, 'w')
for ext, stream in data.iteritems():
fname = "%s.%s" % (name, ext)
if (isinstance(stream, basestring)):
zip_file.write(stream, fname)
else:
zip_file.writestr(fname, stream.read())
zip_file.close()
return f
def atom_link(node):
if 'href' in node.attrib:
return node.attrib['href']
else:
l = node.find("{http://www.w3.org/2005/Atom}link")
return l.get('href')
def atom_link_xml(builder, href):
builder.start("atom:link", {
'rel': 'alternate',
'href': href,
'type': 'application/xml',
'xmlns:atom': 'http://www.w3.org/2005/Atom'
})
builder.end("atom:link")
def bbox_xml(builder, box):
minx, maxx, miny, maxy, crs = box
builder.start("minx", dict())
builder.data(minx)
builder.end("minx")
builder.start("maxx", dict())
builder.data(maxx)
builder.end("maxx")
builder.start("miny", dict())
builder.data(miny)
builder.end("miny")
builder.start("maxy", dict())
builder.data(maxy)
builder.end("maxy")
if crs is not None:
builder.start("crs", {"class": "projected"})
builder.data(crs)
builder.end("crs")

View File

@ -0,0 +1,5 @@
# shapefile_and_friends = None
# shapefile_plus_sidecars = shapefile_and_friends("test/data/states")
def shapefile_and_friends(path):
return dict((ext, path + "." + ext) for ext in ['shx', 'shp', 'dbf', 'prj'])

View File

@ -0,0 +1,33 @@
from sextante.servertools.geoserver.support import xml_property, write_bool, ResourceInfo, url
def workspace_from_index(catalog, node):
name = node.find("name")
return Workspace(catalog, name.text)
class Workspace(ResourceInfo):
resource_type = "workspace"
def __init__(self, catalog, name):
super(Workspace, self).__init__()
self.catalog = catalog
self.name = name
@property
def href(self):
return url(self.catalog.service_url, ["workspaces", self.name + ".xml"])
@property
def coveragestore_url(self):
return url(self.catalog.service_url, ["workspaces", self.name, "coveragestores.xml"])
@property
def datastore_url(self):
return url(self.catalog.service_url, ["workspaces", self.name, "datastores.xml"])
enabled = xml_property("enabled", lambda x: x.lower() == 'true')
writers = dict(
enabled = write_bool("enabled")
)
def __repr__(self):
return "%s @ %s" % (self.name, self.href)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,110 @@
"""
iri2uri
Converts an IRI to a URI.
"""
__author__ = "Joe Gregorio (joe@bitworking.org)"
__copyright__ = "Copyright 2006, Joe Gregorio"
__contributors__ = []
__version__ = "1.0.0"
__license__ = "MIT"
__history__ = """
"""
import urlparse
# Convert an IRI to a URI following the rules in RFC 3987
#
# The characters we need to enocde and escape are defined in the spec:
#
# iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD
# ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF
# / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
# / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
# / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
# / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
# / %xD0000-DFFFD / %xE1000-EFFFD
escape_range = [
(0xA0, 0xD7FF ),
(0xE000, 0xF8FF ),
(0xF900, 0xFDCF ),
(0xFDF0, 0xFFEF),
(0x10000, 0x1FFFD ),
(0x20000, 0x2FFFD ),
(0x30000, 0x3FFFD),
(0x40000, 0x4FFFD ),
(0x50000, 0x5FFFD ),
(0x60000, 0x6FFFD),
(0x70000, 0x7FFFD ),
(0x80000, 0x8FFFD ),
(0x90000, 0x9FFFD),
(0xA0000, 0xAFFFD ),
(0xB0000, 0xBFFFD ),
(0xC0000, 0xCFFFD),
(0xD0000, 0xDFFFD ),
(0xE1000, 0xEFFFD),
(0xF0000, 0xFFFFD ),
(0x100000, 0x10FFFD)
]
def encode(c):
retval = c
i = ord(c)
for low, high in escape_range:
if i < low:
break
if i >= low and i <= high:
retval = "".join(["%%%2X" % ord(o) for o in c.encode('utf-8')])
break
return retval
def iri2uri(uri):
"""Convert an IRI to a URI. Note that IRIs must be
passed in a unicode strings. That is, do not utf-8 encode
the IRI before passing it into the function."""
if isinstance(uri ,unicode):
(scheme, authority, path, query, fragment) = urlparse.urlsplit(uri)
authority = authority.encode('idna')
# For each character in 'ucschar' or 'iprivate'
# 1. encode as utf-8
# 2. then %-encode each octet of that utf-8
uri = urlparse.urlunsplit((scheme, authority, path, query, fragment))
uri = "".join([encode(c) for c in uri])
return uri
if __name__ == "__main__":
import unittest
class Test(unittest.TestCase):
def test_uris(self):
"""Test that URIs are invariant under the transformation."""
invariant = [
u"ftp://ftp.is.co.za/rfc/rfc1808.txt",
u"http://www.ietf.org/rfc/rfc2396.txt",
u"ldap://[2001:db8::7]/c=GB?objectClass?one",
u"mailto:John.Doe@example.com",
u"news:comp.infosystems.www.servers.unix",
u"tel:+1-816-555-1212",
u"telnet://192.0.2.16:80/",
u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ]
for uri in invariant:
self.assertEqual(uri, iri2uri(uri))
def test_iri(self):
""" Test that the right type of escaping is done for each part of the URI."""
self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}"))
self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}"))
self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}"))
self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}"))
self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))
self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")))
self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8')))
unittest.main()

View File

@ -0,0 +1,438 @@
"""SocksiPy - Python SOCKS module.
Version 1.00
Copyright 2006 Dan-Haim. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of Dan Haim nor the names of his contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
This module provides a standard socket-like interface for Python
for tunneling connections through SOCKS proxies.
"""
"""
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
for use in PyLoris (http://pyloris.sourceforge.net/)
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
mainly to merge bug fixes found in Sourceforge
"""
import base64
import socket
import struct
import sys
if getattr(socket, 'socket', None) is None:
raise ImportError('socket.socket missing, proxy support unusable')
PROXY_TYPE_SOCKS4 = 1
PROXY_TYPE_SOCKS5 = 2
PROXY_TYPE_HTTP = 3
PROXY_TYPE_HTTP_NO_TUNNEL = 4
_defaultproxy = None
_orgsocket = socket.socket
class ProxyError(Exception): pass
class GeneralProxyError(ProxyError): pass
class Socks5AuthError(ProxyError): pass
class Socks5Error(ProxyError): pass
class Socks4Error(ProxyError): pass
class HTTPError(ProxyError): pass
_generalerrors = ("success",
"invalid data",
"not connected",
"not available",
"bad proxy type",
"bad input")
_socks5errors = ("succeeded",
"general SOCKS server failure",
"connection not allowed by ruleset",
"Network unreachable",
"Host unreachable",
"Connection refused",
"TTL expired",
"Command not supported",
"Address type not supported",
"Unknown error")
_socks5autherrors = ("succeeded",
"authentication is required",
"all offered authentication methods were rejected",
"unknown username or invalid password",
"unknown error")
_socks4errors = ("request granted",
"request rejected or failed",
"request rejected because SOCKS server cannot connect to identd on the client",
"request rejected because the client program and identd report different user-ids",
"unknown error")
def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
"""setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
Sets a default proxy which all further socksocket objects will use,
unless explicitly changed.
"""
global _defaultproxy
_defaultproxy = (proxytype, addr, port, rdns, username, password)
def wrapmodule(module):
"""wrapmodule(module)
Attempts to replace a module's socket library with a SOCKS socket. Must set
a default proxy using setdefaultproxy(...) first.
This will only work on modules that import socket directly into the namespace;
most of the Python Standard Library falls into this category.
"""
if _defaultproxy != None:
module.socket.socket = socksocket
else:
raise GeneralProxyError((4, "no proxy specified"))
class socksocket(socket.socket):
"""socksocket([family[, type[, proto]]]) -> socket object
Open a SOCKS enabled socket. The parameters are the same as
those of the standard socket init. In order for SOCKS to work,
you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
"""
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
_orgsocket.__init__(self, family, type, proto, _sock)
if _defaultproxy != None:
self.__proxy = _defaultproxy
else:
self.__proxy = (None, None, None, None, None, None)
self.__proxysockname = None
self.__proxypeername = None
self.__httptunnel = True
def __recvall(self, count):
"""__recvall(count) -> data
Receive EXACTLY the number of bytes requested from the socket.
Blocks until the required number of bytes have been received.
"""
data = self.recv(count)
while len(data) < count:
d = self.recv(count-len(data))
if not d: raise GeneralProxyError((0, "connection closed unexpectedly"))
data = data + d
return data
def sendall(self, content, *args):
""" override socket.socket.sendall method to rewrite the header
for non-tunneling proxies if needed
"""
if not self.__httptunnel:
content = self.__rewriteproxy(content)
return super(socksocket, self).sendall(content, *args)
def __rewriteproxy(self, header):
""" rewrite HTTP request headers to support non-tunneling proxies
(i.e. those which do not support the CONNECT method).
This only works for HTTP (not HTTPS) since HTTPS requires tunneling.
"""
host, endpt = None, None
hdrs = header.split("\r\n")
for hdr in hdrs:
if hdr.lower().startswith("host:"):
host = hdr
elif hdr.lower().startswith("get") or hdr.lower().startswith("post"):
endpt = hdr
if host and endpt:
hdrs.remove(host)
hdrs.remove(endpt)
host = host.split(" ")[1]
endpt = endpt.split(" ")
if (self.__proxy[4] != None and self.__proxy[5] != None):
hdrs.insert(0, self.__getauthheader())
hdrs.insert(0, "Host: %s" % host)
hdrs.insert(0, "%s http://%s%s %s" % (endpt[0], host, endpt[1], endpt[2]))
return "\r\n".join(hdrs)
def __getauthheader(self):
auth = self.__proxy[4] + ":" + self.__proxy[5]
return "Proxy-Authorization: Basic " + base64.b64encode(auth)
def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
"""setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
Sets the proxy to be used.
proxytype - The type of the proxy to be used. Three types
are supported: PROXY_TYPE_SOCKS4 (including socks4a),
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
addr - The address of the server (IP or DNS).
port - The port of the server. Defaults to 1080 for SOCKS
servers and 8080 for HTTP proxy servers.
rdns - Should DNS queries be preformed on the remote side
(rather than the local side). The default is True.
Note: This has no effect with SOCKS4 servers.
username - Username to authenticate with to the server.
The default is no authentication.
password - Password to authenticate with to the server.
Only relevant when username is also provided.
"""
self.__proxy = (proxytype, addr, port, rdns, username, password)
def __negotiatesocks5(self, destaddr, destport):
"""__negotiatesocks5(self,destaddr,destport)
Negotiates a connection through a SOCKS5 server.
"""
# First we'll send the authentication packages we support.
if (self.__proxy[4]!=None) and (self.__proxy[5]!=None):
# The username/password details were supplied to the
# setproxy method so we support the USERNAME/PASSWORD
# authentication (in addition to the standard none).
self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
else:
# No username/password were entered, therefore we
# only support connections with no authentication.
self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00))
# We'll receive the server's response to determine which
# method was selected
chosenauth = self.__recvall(2)
if chosenauth[0:1] != chr(0x05).encode():
self.close()
raise GeneralProxyError((1, _generalerrors[1]))
# Check the chosen authentication method
if chosenauth[1:2] == chr(0x00).encode():
# No authentication is required
pass
elif chosenauth[1:2] == chr(0x02).encode():
# Okay, we need to perform a basic username/password
# authentication.
self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5])
authstat = self.__recvall(2)
if authstat[0:1] != chr(0x01).encode():
# Bad response
self.close()
raise GeneralProxyError((1, _generalerrors[1]))
if authstat[1:2] != chr(0x00).encode():
# Authentication failed
self.close()
raise Socks5AuthError((3, _socks5autherrors[3]))
# Authentication succeeded
else:
# Reaching here is always bad
self.close()
if chosenauth[1] == chr(0xFF).encode():
raise Socks5AuthError((2, _socks5autherrors[2]))
else:
raise GeneralProxyError((1, _generalerrors[1]))
# Now we can request the actual connection
req = struct.pack('BBB', 0x05, 0x01, 0x00)
# If the given destination address is an IP address, we'll
# use the IPv4 address request even if remote resolving was specified.
try:
ipaddr = socket.inet_aton(destaddr)
req = req + chr(0x01).encode() + ipaddr
except socket.error:
# Well it's not an IP number, so it's probably a DNS name.
if self.__proxy[3]:
# Resolve remotely
ipaddr = None
req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr
else:
# Resolve locally
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
req = req + chr(0x01).encode() + ipaddr
req = req + struct.pack(">H", destport)
self.sendall(req)
# Get the response
resp = self.__recvall(4)
if resp[0:1] != chr(0x05).encode():
self.close()
raise GeneralProxyError((1, _generalerrors[1]))
elif resp[1:2] != chr(0x00).encode():
# Connection failed
self.close()
if ord(resp[1:2])<=8:
raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
else:
raise Socks5Error((9, _socks5errors[9]))
# Get the bound address/port
elif resp[3:4] == chr(0x01).encode():
boundaddr = self.__recvall(4)
elif resp[3:4] == chr(0x03).encode():
resp = resp + self.recv(1)
boundaddr = self.__recvall(ord(resp[4:5]))
else:
self.close()
raise GeneralProxyError((1,_generalerrors[1]))
boundport = struct.unpack(">H", self.__recvall(2))[0]
self.__proxysockname = (boundaddr, boundport)
if ipaddr != None:
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
else:
self.__proxypeername = (destaddr, destport)
def getproxysockname(self):
"""getsockname() -> address info
Returns the bound IP address and port number at the proxy.
"""
return self.__proxysockname
def getproxypeername(self):
"""getproxypeername() -> address info
Returns the IP and port number of the proxy.
"""
return _orgsocket.getpeername(self)
def getpeername(self):
"""getpeername() -> address info
Returns the IP address and port number of the destination
machine (note: getproxypeername returns the proxy)
"""
return self.__proxypeername
def __negotiatesocks4(self,destaddr,destport):
"""__negotiatesocks4(self,destaddr,destport)
Negotiates a connection through a SOCKS4 server.
"""
# Check if the destination address provided is an IP address
rmtrslv = False
try:
ipaddr = socket.inet_aton(destaddr)
except socket.error:
# It's a DNS name. Check where it should be resolved.
if self.__proxy[3]:
ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
rmtrslv = True
else:
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
# Construct the request packet
req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
# The username parameter is considered userid for SOCKS4
if self.__proxy[4] != None:
req = req + self.__proxy[4]
req = req + chr(0x00).encode()
# DNS name if remote resolving is required
# NOTE: This is actually an extension to the SOCKS4 protocol
# called SOCKS4A and may not be supported in all cases.
if rmtrslv:
req = req + destaddr + chr(0x00).encode()
self.sendall(req)
# Get the response from the server
resp = self.__recvall(8)
if resp[0:1] != chr(0x00).encode():
# Bad data
self.close()
raise GeneralProxyError((1,_generalerrors[1]))
if resp[1:2] != chr(0x5A).encode():
# Server returned an error
self.close()
if ord(resp[1:2]) in (91, 92, 93):
self.close()
raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
else:
raise Socks4Error((94, _socks4errors[4]))
# Get the bound address/port
self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
if rmtrslv != None:
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
else:
self.__proxypeername = (destaddr, destport)
def __negotiatehttp(self, destaddr, destport):
"""__negotiatehttp(self,destaddr,destport)
Negotiates a connection through an HTTP server.
"""
# If we need to resolve locally, we do this now
if not self.__proxy[3]:
addr = socket.gethostbyname(destaddr)
else:
addr = destaddr
headers = ["CONNECT ", addr, ":", str(destport), " HTTP/1.1\r\n"]
headers += ["Host: ", destaddr, "\r\n"]
if (self.__proxy[4] != None and self.__proxy[5] != None):
headers += [self.__getauthheader(), "\r\n"]
headers.append("\r\n")
self.sendall("".join(headers).encode())
# We read the response until we get the string "\r\n\r\n"
resp = self.recv(1)
while resp.find("\r\n\r\n".encode()) == -1:
resp = resp + self.recv(1)
# We just need the first line to check if the connection
# was successful
statusline = resp.splitlines()[0].split(" ".encode(), 2)
if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
self.close()
raise GeneralProxyError((1, _generalerrors[1]))
try:
statuscode = int(statusline[1])
except ValueError:
self.close()
raise GeneralProxyError((1, _generalerrors[1]))
if statuscode != 200:
self.close()
raise HTTPError((statuscode, statusline[2]))
self.__proxysockname = ("0.0.0.0", 0)
self.__proxypeername = (addr, destport)
def connect(self, destpair):
"""connect(self, despair)
Connects to the specified destination through a proxy.
destpar - A tuple of the IP/DNS address and the port number.
(identical to socket's connect).
To select the proxy server use setproxy().
"""
# Do a minimal input check first
if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (not isinstance(destpair[0], basestring)) or (type(destpair[1]) != int):
raise GeneralProxyError((5, _generalerrors[5]))
if self.__proxy[0] == PROXY_TYPE_SOCKS5:
if self.__proxy[2] != None:
portnum = self.__proxy[2]
else:
portnum = 1080
_orgsocket.connect(self, (self.__proxy[1], portnum))
self.__negotiatesocks5(destpair[0], destpair[1])
elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
if self.__proxy[2] != None:
portnum = self.__proxy[2]
else:
portnum = 1080
_orgsocket.connect(self,(self.__proxy[1], portnum))
self.__negotiatesocks4(destpair[0], destpair[1])
elif self.__proxy[0] == PROXY_TYPE_HTTP:
if self.__proxy[2] != None:
portnum = self.__proxy[2]
else:
portnum = 8080
_orgsocket.connect(self,(self.__proxy[1], portnum))
self.__negotiatehttp(destpair[0], destpair[1])
elif self.__proxy[0] == PROXY_TYPE_HTTP_NO_TUNNEL:
if self.__proxy[2] != None:
portnum = self.__proxy[2]
else:
portnum = 8080
_orgsocket.connect(self,(self.__proxy[1],portnum))
if destpair[1] == 443:
self.__negotiatehttp(destpair[0],destpair[1])
else:
self.__httptunnel = False
elif self.__proxy[0] == None:
_orgsocket.connect(self, (destpair[0], destpair[1]))
else:
raise GeneralProxyError((4, _generalerrors[4]))

View File

@ -1,104 +0,0 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
ExampleAlgorithm.py
---------------------
Date : August 2012
Copyright : (C) 2012 by Tim Sutton
Email : tim at linfiniti dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Tim Sutton'
__date__ = 'August 2012'
__copyright__ = '(C) 2012, Tim Sutton'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from sextante.core.GeoAlgorithm import GeoAlgorithm
from sextante.outputs.OutputVector import OutputVector
from sextante.parameters.ParameterVector import ParameterVector
from sextante.core.Sextante import Sextante
from qgis.core import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from sextante.parameters.ParameterExtent import ParameterExtent
from sextante.parameters.ParameterCrs import ParameterCrs
class ExampleAlgorithm(GeoAlgorithm):
'''This is an example algorithm that takes a vector layer and creates
a new one just with just those features of the input layer that are
selected.
It is meant to be used as an example of how to create your own SEXTANTE
algorithms and explain methods and variables used to do it.
An algorithm like this will be available in all SEXTANTE elements, and
there is not need for additional work.
All SEXTANTE algorithms should extend the GeoAlgorithm class'''
#constants used to refer to parameters and outputs.
#They will be used when calling the algorithm from another algorithm,
#or when calling SEXTANTE from the QGIS console.
OUTPUT_LAYER = "OUTPUT_LAYER"
INPUT_LAYER = "INPUT_LAYER"
def defineCharacteristics(self):
'''Here we define the inputs and output of the algorithm, along
with some other properties'''
#the name that the user will see in the toolbox
self.name = "Create new layer with selected features"
#the branch of the toolbox under which the algorithm will appear
self.group = "Algorithms for vector layers"
#we add the input vector layer. It can have any kind of geometry
#It is a mandatory (not optional) one, hence the False argument
self.addParameter(ParameterVector(self.INPUT_LAYER, "Input layer", ParameterVector.VECTOR_TYPE_ANY, False))
self.addParameter(ParameterExtent("EXTENT","EXTENT"))
self.addParameter(ParameterCrs("CRS", "CRS"))
# we add a vector layer as output
self.addOutput(OutputVector(self.OUTPUT_LAYER, "Output layer with selected features"))
def processAlgorithm(self, progress):
'''Here is where the processing itself takes place'''
#the first thing to do is retrieve the values of the parameters
#entered by the user
inputFilename = self.getParameterValue(self.INPUT_LAYER)
output = self.getOutputValue(self.OUTPUT_LAYER)
#input layers values are always a string with its location.
#That string can be converted into a QGIS object (a QgsVectorLayer in this case))
#using the Sextante.getObject() method
vectorLayer = Sextante.getObject(inputFilename)
#And now we can process
#First we create the output layer.
#The output value entered by the user is a string containing a filename,
#so we can use it directly
settings = QSettings()
systemEncoding = settings.value( "/UI/encoding", "System" ).toString()
provider = vectorLayer.dataProvider()
writer = QgsVectorFileWriter( output, systemEncoding, provider.fields(), provider.geometryType(), provider.crs() )
#Now we take the selected features and add them to the output layer
selection = vectorLayer.selectedFeatures()
for feat in selection:
writer.addFeature(feat)
del writer
#There is nothing more to do here. We do not have to open the layer that we have created.
#SEXTANTE will take care of that, or will handle it if this algorithm is executed within
#a complex model

View File

@ -1,78 +0,0 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
ExampleAlgorithmProvider.py
---------------------
Date : August 2012
Copyright : (C) 2012 by Tim Sutton
Email : tim at linfiniti dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Tim Sutton'
__date__ = 'August 2012'
__copyright__ = '(C) 2012, Tim Sutton'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from sextante.core.AlgorithmProvider import AlgorithmProvider
from sextanteexampleprovider.ExampleAlgorithm import ExampleAlgorithm
from sextante.core.SextanteConfig import Setting, SextanteConfig
class ExampleAlgorithmProvider(AlgorithmProvider):
MY_DUMMY_SETTING = "MY_DUMMY_SETTING"
def __init__(self):
AlgorithmProvider.__init__(self)
self.alglist = [ExampleAlgorithm()]
def initializeSettings(self):
'''In this method we add settings needed to configure our provider.
Do not forget to call the parent method, since it takes care or
automatically adding a setting for activating or deactivating the
algorithms in the provider'''
AlgorithmProvider.initializeSettings(self)
SextanteConfig.addSetting(Setting("Example algorithms", ExampleAlgorithmProvider.MY_DUMMY_SETTING, "Example setting", "Default value"))
'''To get the parameter of a setting parameter, use SextanteConfig.getSetting(name_of_parameter)'''
def unload(self):
'''Setting should be removed here, so they do not appear anymore
when the plugin is unloaded'''
AlgorithmProvider.unload(self)
SextanteConfig.removeSetting( ExampleAlgorithmProvider.MY_DUMMY_SETTING)
def getName(self):
'''This name is used to create the command line name of all the algorithms
from this provider'''
return "exampleprovider"
def getDescription(self):
'''This is the name that will appear on the toolbox group.'''
return "Example algorithms"
def getIcon(self):
'''We return the default icon'''
return AlgorithmProvider.getIcon(self)
def _loadAlgorithms(self):
'''Here we fill the list of algorithms in self.algs.
This method is called whenever the list of algorithms should be updated.
If the list of algorithms can change while executing SEXTANTE for QGIS
(for instance, if it contains algorithms from user-defined scripts and
a new script might have been added), you should create the list again
here.
In this case, since the list is always the same, we assign from the pre-made list.
This assignment has to be done in this method even if the list does not change,
since the self.algs list is cleared before calling this method'''
self.algs = self.alglist

View File

@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
SextanteExampleProviderPlugin.py
---------------------
Date : August 2012
Copyright : (C) 2012 by Tim Sutton
Email : tim at linfiniti dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Tim Sutton'
__date__ = 'August 2012'
__copyright__ = '(C) 2012, Tim Sutton'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from qgis.core import *
import os, sys
import inspect
from sextante.core.Sextante import Sextante
from sextanteexampleprovider.ExampleAlgorithmProvider import ExampleAlgorithmProvider
cmd_folder = os.path.split(inspect.getfile( inspect.currentframe() ))[0]
if cmd_folder not in sys.path:
sys.path.insert(0, cmd_folder)
class SextanteExampleProviderPlugin:
def __init__(self):
self.provider = ExampleAlgorithmProvider()
def initGui(self):
Sextante.addProvider(self.provider)
def unload(self):
Sextante.removeProvider(self.provider)

View File

@ -1,11 +0,0 @@
[general]
name=SEXTANTE Example Provider
description=An example plugin that adds algorithms to SEXTANTE. Mainly created to guide developers in the process of creating plugins that add new capabilities to SEXTANTE
version=1.0
qgisMinimumVersion=1.0
name=Victor Olaya
email=volayaf@gmail.com
website=www.sextantegis.com
class_name=SextanteExampleProviderPlugin