work in progress: process upload of resources from loops.agent via HTTP PUT
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1953 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
c3ffee3437
commit
1d0ff2aed2
6 changed files with 196 additions and 17 deletions
|
@ -140,7 +140,10 @@ Uploading Resources with HTTP PUT Requests
|
||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
>>> from zope.publisher.browser import TestRequest
|
>>> from zope.publisher.browser import TestRequest
|
||||||
|
>>> from zope.traversing.api import getName
|
||||||
>>> from loops.integrator.put import ResourceManagerTraverser
|
>>> from loops.integrator.put import ResourceManagerTraverser
|
||||||
|
>>> from loops.integrator.source import ExternalSourceInfo
|
||||||
|
>>> component.provideAdapter(ExternalSourceInfo)
|
||||||
|
|
||||||
>>> rrinfo = 'local/user/filesystem'
|
>>> rrinfo = 'local/user/filesystem'
|
||||||
>>> rrpath = 'testing/data/file1.txt'
|
>>> rrpath = 'testing/data/file1.txt'
|
||||||
|
@ -154,9 +157,14 @@ Uploading Resources with HTTP PUT Requests
|
||||||
>>> request._traversal_stack = list(reversed(rrid.split('/')))
|
>>> request._traversal_stack = list(reversed(rrid.split('/')))
|
||||||
|
|
||||||
>>> traverser = ResourceManagerTraverser(resources, request)
|
>>> traverser = ResourceManagerTraverser(resources, request)
|
||||||
>>> traverser.publishTraverse(request, '.data')
|
>>> resource = traverser.publishTraverse(request, '.data')
|
||||||
*** resources.PUT .data testing,data,file1.txt local user filesystem
|
*** resources.PUT .data local/user/filesystem/testing/data/file1.txt
|
||||||
<...DummyWriteFile object ...>
|
|
||||||
|
>>> getName(resource)
|
||||||
|
u'local_user_filesystem_testing_data_file1.txt'
|
||||||
|
>>> resource.title
|
||||||
|
u'file1'
|
||||||
|
|
||||||
|
|
||||||
Fin de partie
|
Fin de partie
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -16,7 +16,21 @@
|
||||||
set_schema="loops.integrator.interfaces.IExternalCollection" />
|
set_schema="loops.integrator.interfaces.IExternalCollection" />
|
||||||
</zope:class>
|
</zope:class>
|
||||||
|
|
||||||
<zope:class class="loops.integrator.put.DummyWriteFile">
|
<zope:adapter factory="loops.integrator.source.ExternalSourceInfo"
|
||||||
|
trusted="True" />
|
||||||
|
|
||||||
|
<zope:class class="loops.integrator.source.ExternalSourceInfo">
|
||||||
|
<require permission="zope.View"
|
||||||
|
interface="loops.integrator.interfaces.IExternalSourceInfo" />
|
||||||
|
<require permission="zope.ManageContent"
|
||||||
|
set_schema="loops.integrator.interfaces.IExternalSourceInfo" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
|
<zope:class class="loops.integrator.put.MetadataProxy">
|
||||||
|
<allow attributes="write" />
|
||||||
|
</zope:class>
|
||||||
|
|
||||||
|
<zope:class class="loops.integrator.put.DummyResource">
|
||||||
<allow attributes="write" />
|
<allow attributes="write" />
|
||||||
</zope:class>
|
</zope:class>
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,22 @@ from zope import interface, component, schema
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
|
||||||
|
|
||||||
|
class IExternalSourceInfo(Interface):
|
||||||
|
""" Provide additional information about the external source
|
||||||
|
of an object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
externalIdentifier = Attribute('A string that allows to uniquely '
|
||||||
|
'identify a resource or concept that is provided by an '
|
||||||
|
'external system, e.g. a client-base loops agent. ')
|
||||||
|
|
||||||
|
|
||||||
|
# external collections
|
||||||
|
|
||||||
class IExternalCollection(Interface):
|
class IExternalCollection(Interface):
|
||||||
""" A collection of resources, to be used for a concept adapter.
|
""" A concept representing a collection of resources that may be
|
||||||
|
actively retrieved from an external system using the parameters
|
||||||
|
given.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
providerName = schema.TextLine(
|
providerName = schema.TextLine(
|
||||||
|
@ -84,6 +98,7 @@ class IExternalCollectionProvider(Interface):
|
||||||
provided. Return the list of objects created.
|
provided. Return the list of objects created.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# classification stuff
|
||||||
|
|
||||||
class IAutoClassifier(Interface):
|
class IAutoClassifier(Interface):
|
||||||
""" An adapter that more or less automagically assigns concepts to a
|
""" An adapter that more or less automagically assigns concepts to a
|
||||||
|
|
|
@ -25,12 +25,21 @@ $Id$
|
||||||
from zope import interface, component
|
from zope import interface, component
|
||||||
from zope.interface import implements
|
from zope.interface import implements
|
||||||
from zope.component import adapts
|
from zope.component import adapts
|
||||||
|
from zope.app.catalog.interfaces import ICatalog
|
||||||
|
from zope.app.container.interfaces import INameChooser
|
||||||
from zope.app.container.traversal import ContainerTraverser, ItemTraverser
|
from zope.app.container.traversal import ContainerTraverser, ItemTraverser
|
||||||
|
from zope.contenttype import guess_content_type
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
from zope.event import notify
|
||||||
from zope.filerepresentation.interfaces import IWriteFile
|
from zope.filerepresentation.interfaces import IWriteFile
|
||||||
|
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
||||||
from zope.publisher.interfaces import IPublishTraverse
|
from zope.publisher.interfaces import IPublishTraverse
|
||||||
|
|
||||||
|
from cybertools.text import mimetypes
|
||||||
|
from loops.common import adapted
|
||||||
|
from loops.integrator.interfaces import IExternalSourceInfo
|
||||||
from loops.interfaces import IResourceManager
|
from loops.interfaces import IResourceManager
|
||||||
|
from loops.resource import Resource
|
||||||
|
|
||||||
|
|
||||||
class ResourceManagerTraverser(ItemTraverser):
|
class ResourceManagerTraverser(ItemTraverser):
|
||||||
|
@ -41,20 +50,94 @@ class ResourceManagerTraverser(ItemTraverser):
|
||||||
def publishTraverse(self, request, name):
|
def publishTraverse(self, request, name):
|
||||||
if request.method == 'PUT':
|
if request.method == 'PUT':
|
||||||
if name.startswith('.'):
|
if name.startswith('.'):
|
||||||
stack = request._traversal_stack
|
return self.getOrCreateResource(request, name)
|
||||||
#stack = request.getTraversalStack()
|
|
||||||
#print '*** resources.PUT', name, '/'.join(stack)
|
|
||||||
machine, user, app = stack.pop(), stack.pop(), stack.pop()
|
|
||||||
path = '/'.join(reversed(stack))
|
|
||||||
path = path.replace(',', ',,').replace('/', ',')
|
|
||||||
for i in range(len(stack)):
|
|
||||||
stack.pop()
|
|
||||||
print '*** resources.PUT', name, path, machine, user, app
|
|
||||||
return DummyWriteFile()
|
|
||||||
return super(ResourceManagerTraverser, self).publishTraverse(request, name)
|
return super(ResourceManagerTraverser, self).publishTraverse(request, name)
|
||||||
|
|
||||||
|
def getOrCreateResource(self, request, name):
|
||||||
|
stack = request._traversal_stack
|
||||||
|
#machine, user, app = stack.pop(), stack.pop(), stack.pop()
|
||||||
|
path = '/'.join(reversed(stack))
|
||||||
|
#path = path.replace(',', ',,').replace('/', ',')
|
||||||
|
for i in range(len(stack)):
|
||||||
|
# prevent further traversal
|
||||||
|
stack.pop()
|
||||||
|
#print '*** resources.PUT', name, path, machine, user, app
|
||||||
|
print '*** resources.PUT', name, path
|
||||||
|
resource = self.findResource(path)
|
||||||
|
if resource is None:
|
||||||
|
resource = self.createResource(path)
|
||||||
|
notify(ObjectModifiedEvent(resource))
|
||||||
|
#resource = DummyResource()
|
||||||
|
if name == '.meta':
|
||||||
|
return MetadataProxy(resource)
|
||||||
|
return resource
|
||||||
|
|
||||||
class DummyWriteFile(object):
|
def findResource(self, identifier):
|
||||||
|
cat = component.getUtility(ICatalog)
|
||||||
|
loopsRoot = self.context.getLoopsRoot()
|
||||||
|
result = cat.searchResults(
|
||||||
|
loops_externalidentifier=(identifier, identifier))
|
||||||
|
result = [r for r in result if r.getLoopsRoot() == loopsRoot]
|
||||||
|
if len(result) > 1:
|
||||||
|
raise ValueError('More than one resource found for external '
|
||||||
|
'identifier %s. Resources found: %s.' %
|
||||||
|
(identifier, str([r.name for r in result])))
|
||||||
|
if len(result) == 0:
|
||||||
|
return None
|
||||||
|
return result[0]
|
||||||
|
|
||||||
|
def createResource(self, identifier):
|
||||||
|
name = self.generateName(identifier)
|
||||||
|
title = self.generateTitle(identifier)
|
||||||
|
contentType = guess_content_type(identifier,
|
||||||
|
default='application/octet-stream')[0]
|
||||||
|
resource = Resource()
|
||||||
|
self.context[name] = resource
|
||||||
|
cm = self.context.getLoopsRoot().getConceptManager()
|
||||||
|
resource.resourceType = cm['extfile']
|
||||||
|
obj = adapted(resource)
|
||||||
|
obj.contentType = contentType
|
||||||
|
obj.title = title
|
||||||
|
#obj.data = data
|
||||||
|
notify(ObjectModifiedEvent(resource))
|
||||||
|
# TODO: provide basic concept assignments (collections)
|
||||||
|
IExternalSourceInfo(resource).externalIdentifier == identifier
|
||||||
|
return resource
|
||||||
|
|
||||||
|
def generateName(self, name):
|
||||||
|
name = INameChooser(self.context).chooseName(name, None)
|
||||||
|
return name
|
||||||
|
|
||||||
|
def generateTitle(self, name):
|
||||||
|
separators = ('/', '\\')
|
||||||
|
for sep in separators:
|
||||||
|
if sep in name:
|
||||||
|
name = name.rsplit(sep, 1)[-1]
|
||||||
|
break
|
||||||
|
if '.' in name:
|
||||||
|
base, ext = name.rsplit('.', 1)
|
||||||
|
if ext.lower() in mimetypes.extensions.values():
|
||||||
|
name = base
|
||||||
|
return name.decode('UTF-8')
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataProxy(object):
|
||||||
|
""" Processes a metadata file for an associated resource.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, resource):
|
||||||
|
self.resource = resource
|
||||||
|
|
||||||
|
implements(IWriteFile)
|
||||||
|
|
||||||
|
def write(self, text):
|
||||||
|
# TODO: provide/change concept assignments based on metadata
|
||||||
|
print '*** metadata', repr(text)
|
||||||
|
|
||||||
|
|
||||||
|
class DummyResource(object):
|
||||||
|
""" Just for testing
|
||||||
|
"""
|
||||||
|
|
||||||
implements(IWriteFile)
|
implements(IWriteFile)
|
||||||
|
|
||||||
|
|
53
integrator/source.py
Normal file
53
integrator/source.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Managing information form objects provided by external sources, e.g. loops.agent.
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
||||||
|
from persistent.mapping import PersistentMapping
|
||||||
|
from zope import interface, component
|
||||||
|
from zope.interface import implements
|
||||||
|
from zope.component import adapts
|
||||||
|
|
||||||
|
from loops.interfaces import ILoopsObject
|
||||||
|
from loops.integrator.interfaces import IExternalSourceInfo
|
||||||
|
|
||||||
|
|
||||||
|
sourceInfoAttrName = '__loops_integrator_sourceinfo__'
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalSourceInfo(object):
|
||||||
|
|
||||||
|
implements(IExternalSourceInfo)
|
||||||
|
adapts(ILoopsObject)
|
||||||
|
|
||||||
|
def __init__(self, context):
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
def getSourceInfo(self):
|
||||||
|
return getattr(self.context, sourceInfoAttrName, PersistentMapping())
|
||||||
|
|
||||||
|
def getExternalIdentifier(self):
|
||||||
|
return self.getSourceInfo().get('externalIdentifier')
|
||||||
|
def setExternalIdentifier(self, value):
|
||||||
|
self.getSourceInfo()['externalIdentifier'] = value
|
||||||
|
externalIdentifier = property(getExternalIdentifier, setExternalIdentifier)
|
||||||
|
|
|
@ -6,6 +6,8 @@ $Id$
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from zope import component
|
from zope import component
|
||||||
|
from zope.app.catalog.interfaces import ICatalog
|
||||||
|
from zope.app.catalog.field import FieldIndex
|
||||||
|
|
||||||
from cybertools.storage.interfaces import IExternalStorage
|
from cybertools.storage.interfaces import IExternalStorage
|
||||||
from cybertools.storage.filesystem import fullPathStorage
|
from cybertools.storage.filesystem import fullPathStorage
|
||||||
|
@ -13,7 +15,7 @@ from loops import util
|
||||||
from loops.interfaces import IFile, IExternalFile
|
from loops.interfaces import IFile, IExternalFile
|
||||||
from loops.concept import Concept
|
from loops.concept import Concept
|
||||||
from loops.resource import Resource, FileAdapter, ExternalFileAdapter
|
from loops.resource import Resource, FileAdapter, ExternalFileAdapter
|
||||||
from loops.integrator.interfaces import IExternalCollection
|
from loops.integrator.interfaces import IExternalSourceInfo, IExternalCollection
|
||||||
from loops.knowledge.setup import SetupManager as KnowledgeSetupManager
|
from loops.knowledge.setup import SetupManager as KnowledgeSetupManager
|
||||||
from loops.setup import SetupManager, addAndConfigureObject
|
from loops.setup import SetupManager, addAndConfigureObject
|
||||||
from loops.tests.setup import TestSite as BaseTestSite
|
from loops.tests.setup import TestSite as BaseTestSite
|
||||||
|
@ -35,6 +37,10 @@ class TestSite(BaseTestSite):
|
||||||
|
|
||||||
component.provideUtility(fullPathStorage(), IExternalStorage, name='fullpath')
|
component.provideUtility(fullPathStorage(), IExternalStorage, name='fullpath')
|
||||||
|
|
||||||
|
catalog = component.getUtility(ICatalog)
|
||||||
|
catalog['loops_externalidentifier'] = FieldIndex('externalIdentifier',
|
||||||
|
IExternalSourceInfo, False)
|
||||||
|
|
||||||
tType = concepts.getTypeConcept()
|
tType = concepts.getTypeConcept()
|
||||||
tExtFile = addAndConfigureObject(concepts, Concept, 'extfile',
|
tExtFile = addAndConfigureObject(concepts, Concept, 'extfile',
|
||||||
title=u'External File', conceptType=tType,
|
title=u'External File', conceptType=tType,
|
||||||
|
|
Loading…
Add table
Reference in a new issue