From 1d0ff2aed2388b732b681d20e73b8d7539e2fff4 Mon Sep 17 00:00:00 2001 From: helmutm Date: Thu, 23 Aug 2007 12:46:12 +0000 Subject: [PATCH] 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 --- integrator/README.txt | 14 +++-- integrator/configure.zcml | 16 +++++- integrator/interfaces.py | 17 +++++- integrator/put.py | 105 ++++++++++++++++++++++++++++++++++---- integrator/source.py | 53 +++++++++++++++++++ integrator/testsetup.py | 8 ++- 6 files changed, 196 insertions(+), 17 deletions(-) create mode 100644 integrator/source.py diff --git a/integrator/README.txt b/integrator/README.txt index 11182b5..45e3e95 100644 --- a/integrator/README.txt +++ b/integrator/README.txt @@ -140,7 +140,10 @@ Uploading Resources with HTTP PUT Requests ========================================== >>> from zope.publisher.browser import TestRequest + >>> from zope.traversing.api import getName >>> from loops.integrator.put import ResourceManagerTraverser + >>> from loops.integrator.source import ExternalSourceInfo + >>> component.provideAdapter(ExternalSourceInfo) >>> rrinfo = 'local/user/filesystem' >>> rrpath = 'testing/data/file1.txt' @@ -154,9 +157,14 @@ Uploading Resources with HTTP PUT Requests >>> request._traversal_stack = list(reversed(rrid.split('/'))) >>> traverser = ResourceManagerTraverser(resources, request) - >>> traverser.publishTraverse(request, '.data') - *** resources.PUT .data testing,data,file1.txt local user filesystem - <...DummyWriteFile object ...> + >>> resource = traverser.publishTraverse(request, '.data') + *** resources.PUT .data local/user/filesystem/testing/data/file1.txt + + >>> getName(resource) + u'local_user_filesystem_testing_data_file1.txt' + >>> resource.title + u'file1' + Fin de partie ============= diff --git a/integrator/configure.zcml b/integrator/configure.zcml index 031b320..5d7f06f 100644 --- a/integrator/configure.zcml +++ b/integrator/configure.zcml @@ -16,7 +16,21 @@ set_schema="loops.integrator.interfaces.IExternalCollection" /> - + + + + + + + + + + + + diff --git a/integrator/interfaces.py b/integrator/interfaces.py index 06c4dd3..f1eeefa 100644 --- a/integrator/interfaces.py +++ b/integrator/interfaces.py @@ -28,8 +28,22 @@ from zope import interface, component, schema 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): - """ 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( @@ -84,6 +98,7 @@ class IExternalCollectionProvider(Interface): provided. Return the list of objects created. """ +# classification stuff class IAutoClassifier(Interface): """ An adapter that more or less automagically assigns concepts to a diff --git a/integrator/put.py b/integrator/put.py index 76e521a..809906d 100644 --- a/integrator/put.py +++ b/integrator/put.py @@ -25,12 +25,21 @@ $Id$ from zope import interface, component from zope.interface import implements 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.contenttype import guess_content_type from zope.cachedescriptors.property import Lazy +from zope.event import notify from zope.filerepresentation.interfaces import IWriteFile +from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent 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.resource import Resource class ResourceManagerTraverser(ItemTraverser): @@ -41,20 +50,94 @@ class ResourceManagerTraverser(ItemTraverser): def publishTraverse(self, request, name): if request.method == 'PUT': if name.startswith('.'): - stack = request._traversal_stack - #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 self.getOrCreateResource(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) diff --git a/integrator/source.py b/integrator/source.py new file mode 100644 index 0000000..0653cbf --- /dev/null +++ b/integrator/source.py @@ -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) + diff --git a/integrator/testsetup.py b/integrator/testsetup.py index 5777a1c..ed453b3 100644 --- a/integrator/testsetup.py +++ b/integrator/testsetup.py @@ -6,6 +6,8 @@ $Id$ import os 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.filesystem import fullPathStorage @@ -13,7 +15,7 @@ from loops import util from loops.interfaces import IFile, IExternalFile from loops.concept import Concept 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.setup import SetupManager, addAndConfigureObject from loops.tests.setup import TestSite as BaseTestSite @@ -35,6 +37,10 @@ class TestSite(BaseTestSite): component.provideUtility(fullPathStorage(), IExternalStorage, name='fullpath') + catalog = component.getUtility(ICatalog) + catalog['loops_externalidentifier'] = FieldIndex('externalIdentifier', + IExternalSourceInfo, False) + tType = concepts.getTypeConcept() tExtFile = addAndConfigureObject(concepts, Concept, 'extfile', title=u'External File', conceptType=tType,