diff --git a/integrator/base.py b/integrator/base.py index 6a84b55..b14b7cb 100644 --- a/integrator/base.py +++ b/integrator/base.py @@ -23,6 +23,7 @@ $Id$ """ import os +from urllib import urlencode from zope.app.container.contained import Contained from zope.cachedescriptors.property import Lazy from zope import component @@ -35,20 +36,40 @@ from cybertools.integrator.interfaces import IReadContainer, IItem, IFile, IImag # proxy base (sample) classes -class ReadContainer(Contained): - - implements(IReadContainer) +class ProxyBase(object): __parent__ = None factoryName = 'sample' - icon = 'folder' + internalPath = '' + externalUrlInfo = None + + description = u'' + authors = () + created = modified = None def __init__(self, address, **kw): self.address = address for k, v in kw.items(): setattr(self, k, v) + @Lazy + def title(self): + if self.internalPath: + return self.internalPath.rsplit('/', 1)[-1] + return self.address.rsplit(os.path.sep, 1)[-1] + + +class ReadContainer(ProxyBase, Contained): + + implements(IReadContainer) + + icon = 'folder' + + @Lazy + def properties(self): + return {} + @Lazy def itemFactory(self): return component.getUtility(IItemFactory, name=self.factoryName) @@ -90,29 +111,21 @@ class ReadContainer(Contained): has_key = __contains__ -class Item(object): +class Item(ProxyBase, object): implements(IItem) - contentType = None icon = 'item' __parent__ = None - def __init__(self, address, **kw): - self.address = address - for k, v in kw.items(): - setattr(self, k, v) - class File(Item): implements(IFile) def __init__(self, address, contentType, **kw): - self.address = address + super(File, self).__init__(address, **kw) self.contentType = contentType - for k, v in kw.items(): - setattr(self, k, v) def getData(self, num=None): return '' @@ -137,6 +150,19 @@ class Image(File): return 0, 0 +# URL info + +class ExternalUrlInfo(object): + + def __init__(self, baseUrl='', path='', params=None): + self.baseUrl, self.path = baseUrl.strip('/'), path.strip('/') + self.params = params or {} + + def __str__(self): + params = self.params and ('?' + urlencode(self.params)) or '' + return '%s/%s%s' % (self.baseUrl, self.path, params) + + # factory base (sample) classes class Factory(object): diff --git a/integrator/bscw.py b/integrator/bscw.py index f75e39f..3e37c29 100644 --- a/integrator/bscw.py +++ b/integrator/bscw.py @@ -32,6 +32,7 @@ from zope.interface import implements, Attribute from cybertools.integrator.base import ContainerFactory, ItemFactory, FileFactory from cybertools.integrator.base import ReadContainer, Item, File, Image +from cybertools.integrator.base import ExternalUrlInfo from cybertools.text import mimetypes @@ -54,14 +55,40 @@ classes = ['cl_core.Folder', 'cl_core.Document', 'cl_core.URL', ] # proxy classes -class ReadContainer(ReadContainer): +class BSCWProxyBase(object): + + @Lazy + def externalUrlInfo(self): + id = self.address + if id.startswith('bs_'): + id = id[3:] + return ExternalUrlInfo(self.baseUrl, id) + + @Lazy + def title(self): + return self.properties['name'] + + @Lazy + def description(self): + return self.properties.get('descr', u'') + + +class ReadContainer(BSCWProxyBase, ReadContainer): factoryName = 'bscw' @Lazy - def data(self): - data = self.server.get_attributes(self.address, + def properties(self): + return self.attributes[0] + + @Lazy + def attributes(self): + return self.server.get_attributes(self.address, ['__class__', 'type', 'id', 'name', 'descr', 'url_link'], 1, True) + + @Lazy + def data(self): + data = self.attributes if len(data) > 1: return dict((item['id'], item) for item in data[1]) else: @@ -83,15 +110,16 @@ class ReadContainer(ReadContainer): return default item = self.data[key] itemType = item['__class__'].split('.')[-1] + internalPath = '/'.join((self.internalPath, key)).strip('/') + params = dict(server=self.server, internalPath=internalPath, + properties=item, baseUrl=self.baseUrl) if itemType == 'Folder': - return self.containerFactory(item['id'], server=self.server, - name=item['name']) + return self.containerFactory(item['id'], **params) elif itemType == 'Document': return self.fileFactory(item['id'], contentType=item['type'], - server=self.server, name=item['name']) + **params) else: - return self.itemFactory(item['id'], server=self.server, - name=item['name'], type=itemType) + return self.itemFactory(item['id'], **params) def values(self): return [self.get(k) for k in self] @@ -106,18 +134,18 @@ class ReadContainer(ReadContainer): return key in self.keys() -class Item(Item): +class Item(BSCWProxyBase, Item): @property def icon(self): return self.type.lower() -class File(File): +class File(BSCWProxyBase, File): contentType = None - def getData(self): + def getData(self, num=None): return self.server.get_document() data = property(getData) @@ -135,7 +163,9 @@ class ContainerFactory(ContainerFactory): server = kw.pop('server') if isinstance(server, basestring): # just a URL, resolve for XML-RPC server = ServerProxy(server) - return self.proxyClass(address, server=server, **kw) + baseUrl = server + baseUrl = kw.pop('baseUrl', '') + return self.proxyClass(address, server=server, baseUrl=baseUrl, **kw) class ItemFactory(ItemFactory): diff --git a/integrator/bscw.txt b/integrator/bscw.txt index 768ea20..3e88633 100644 --- a/integrator/bscw.txt +++ b/integrator/bscw.txt @@ -34,13 +34,29 @@ Let's first register the proxy factory utilities. We can now access the root object of the BSCW repository >>> from cybertools.integrator.interfaces import IContainerFactory - >>> root = component.getUtility(IContainerFactory, name='bscw')('4', server=server) + >>> root = component.getUtility(IContainerFactory, name='bscw')('4', server=server, + ... baseUrl='http://localhost/bscw.cgi/') >>> sorted(root.items()) [('bs_5', <...ReadContainer object...>)] + >>> root.address + '4' + >>> root.internalPath + '' >>> root.icon 'folder' + >>> root.properties + {...'name': 'public'...} + >>> root.title + 'public' + >>> root.description + 'Public Repository' + + >>> str(root.externalUrlInfo) + 'http://localhost/bscw.cgi/4' + +Let's also have a look at the item contained in the root object. >>> bs_5 = root['bs_5'] >>> data = server.get_attributes('bs_5', @@ -48,5 +64,15 @@ We can now access the root object of the BSCW repository >>> bs_5.items() [] + >>> bs_5.address + 'bs_5' + >>> bs_5.internalPath + 'bs_5' >>> bs_5.icon 'folder' + >>> bs_5.properties + {...'name': 'Introduction'...} + + >>> str(bs_5.externalUrlInfo) + 'http://localhost/bscw.cgi/5' + diff --git a/integrator/filesystem.py b/integrator/filesystem.py index 25bd605..6507074 100644 --- a/integrator/filesystem.py +++ b/integrator/filesystem.py @@ -60,10 +60,13 @@ class ReadContainer(ReadContainer): if key not in self.keys(): return default path = os.path.join(self.address, key) + internalPath = '/'.join((self.internalPath, key)).strip('/') if os.path.isdir(path): - return self.containerFactory(path, __parent__=self.__parent__) + return self.containerFactory(path, internalPath=internalPath, + __parent__=self.__parent__) else: - return self.fileFactory(path, __parent__=self.__parent__) + return self.fileFactory(path, internalPath=internalPath, + __parent__=self.__parent__) def values(self): return [self.get(k) for k in self] diff --git a/integrator/filesystem.txt b/integrator/filesystem.txt index bc8ecfe..6c23211 100644 --- a/integrator/filesystem.txt +++ b/integrator/filesystem.txt @@ -24,33 +24,81 @@ Let's do some basic set up Accessing Objects in the Filesystem =================================== +We access the top-level object (a directory) by calling the container (proxy) +factory with the address (path) leading to the directory. + >>> top = component.getUtility(IContainerFactory, name='filesystem')(testDir) + +This top-level object is a container with some sub-objects, that may be +containers themeselves or terminal objects (items or files). + >>> sorted(top) ['index.html', 'sub'] - >>> len(top) - 2 + +A proxy provides a set of attributes that may be used for viewing the +object or navigating to it. + + >>> top.address + '...data' + >>> top.internalPath + '' + >>> top.icon + 'folder' + >>> top.properties + {} + >>> top.title + 'data' + +The external URL information may be used for directly linking to the +external object - in the case of filesystem objects this is not possible +in a general way, so this attribute is always None. + + >>> top.externalUrlInfo is None + True + +Let's now have a look at the sub-objects found in the top-level container. >>> sub = top['sub'] >>> sorted(sub) ['demo.tgz', 'index.html', 'loops_logo.png'] + >>> sub.address + '...sub' + >>> sub.internalPath + 'sub' + >>> sub.icon + 'folder' + >>> sub.properties + {} + >>> sub.externalUrlInfo is None + True + +A file object has additional attributes/methods. >>> file = sub['demo.tgz'] + >>> file.address + '...demo.tgz' + >>> file.internalPath + 'sub/demo.tgz' + >>> file.icon + 'tar' >>> file.contentType 'application/x-tar' >>> file.getSize() 432L - >>> file.icon - 'tar' >>> logo = sub['loops_logo.png'] + >>> logo.internalPath + 'sub/loops_logo.png' + >>> logo.icon + 'image' >>> logo.contentType 'image/png' >>> logo.getImageSize() (145, 42) - >>> logo.icon - 'image' >>> html = top['index.html'] + >>> html.internalPath + 'index.html' >>> html.contentType 'text/html' >>> print html.data diff --git a/integrator/interfaces.py b/integrator/interfaces.py index 6a57cdd..390ebfa 100644 --- a/integrator/interfaces.py +++ b/integrator/interfaces.py @@ -29,9 +29,39 @@ from zope.interface import Interface, Attribute class IProxy(Interface): + """ An object that represents an external object and provides access + to it. + """ + address = Attribute('An external specifier (a name, path, URL, ...) ' + 'that may be used to access the external object.') + internalPath = Attribute('A relativ path leading to an internal ' + 'representation of the represented object.') icon = Attribute('The name of an icon that may be used for symbolizing ' - 'this object.') + 'the represented object.') + properties = Attribute('A dictionary with attributes/properties ' + 'characteristic for the type of object the proxy represents.') + externalUrlInfo = Attribute('Information necessary for building URLs that link ' + 'directly to the object the proxy represents (optional).') + + title = Attribute('A short string giving basic information about the object.') + description = Attribute('A somewhat longer descriptive information.') + url = Attribute('An explicit target URL (optional).') + authors = Attribute('A list of names of persons who worked on this object.') + create = Attribute('A datetime object denoting the data/time of object ' + 'creation.') + modified = Attribute('A datetime object denoting the data/time of last ' + 'modification of the object.') + + +class IExternalUrlInfo(Interface): + """ Information necessary to build URLs to external objects. + """ + baseUrl = Attribute('The base part of the URL, including the protocol and ' + 'the server part + optionally a constant part of the path.') + path = Attribute('The relative path leading to the object.') + params = Attribute('A dictionary providing a parameter set that will ' + 'be appended urlencoded to the base URL.') class IReadContainer(IProxy, IReadContainer): @@ -40,19 +70,23 @@ class IReadContainer(IProxy, IReadContainer): class IItem(IProxy, Interface): - """ A terminal kind of object, i.e. not a container of other objects. + """ A proxy for a terminal kind of object, i.e. not a container of + other objects. """ class IFile(IItem, IFile, IFileContent): + data = Attribute('The data contained in the file.') contentType = Attribute('The MIME type of the object.') - def getData(num): + def getData(num=None): """ Return num bytes from the file`s data. """ +# proxy factories + class IProxyFactory(Interface): """ Creates proxy objects for external objects. """