diff --git a/integrator/README.txt b/integrator/README.txt
new file mode 100644
index 0000000..ff4e43d
--- /dev/null
+++ b/integrator/README.txt
@@ -0,0 +1,58 @@
+=========================================
+Integrating objects from external systems
+=========================================
+
+Integration of external sources.
+
+ ($Id$)
+
+
+Getting started
+===============
+
+Let's do some basic set up
+
+ >>> from zope import component, interface
+
+ >>> from cybertools.integrator.tests import testDir
+ >>> from cybertools.integrator.filesystem import ContainerFactory, FileFactory
+ >>> from cybertools.integrator.interfaces import IContainerFactory
+ >>> component.provideUtility(ContainerFactory(), name='filesystem')
+ >>> component.provideUtility(FileFactory(), name='filesystem')
+
+
+Accessing Objects in the Filesystem
+=======================================
+
+ >>> top = component.getUtility(IContainerFactory, name='filesystem')(testDir)
+ >>> sorted(top)
+ ['index.html', 'sub']
+ >>> len(top)
+ 2
+
+ >>> sub = top['sub']
+ >>> sorted(sub)
+ ['demo.tgz', 'index.html', 'loops_logo.png']
+
+ >>> file = sub['demo.tgz']
+ >>> file.contentType
+ 'application/x-tar'
+ >>> file.getSize()
+ 432L
+
+ >>> logo = sub['loops_logo.png']
+ >>> logo.contentType
+ 'image/png'
+ >>> logo.getImageSize()
+ (145, 42)
+
+ >>> html = top['index.html']
+ >>> html.contentType
+ 'text/html'
+ >>> print html.data
+ ...
+
+ Subdirectory
+ Demo...
+ ...
+
diff --git a/integrator/__init__.py b/integrator/__init__.py
new file mode 100644
index 0000000..38314f3
--- /dev/null
+++ b/integrator/__init__.py
@@ -0,0 +1,3 @@
+"""
+$Id$
+"""
diff --git a/integrator/base.py b/integrator/base.py
new file mode 100644
index 0000000..59a99bd
--- /dev/null
+++ b/integrator/base.py
@@ -0,0 +1,134 @@
+#
+# Copyright (c) 2008 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
+#
+
+"""
+Base implementation for accessing external content objects.
+
+$Id$
+"""
+
+from zope.app.container.contained import Contained
+from zope.cachedescriptors.property import Lazy
+from zope import component
+from zope.interface import implements
+
+from cybertools.integrator.interfaces import IContainerFactory, IFileFactory
+from cybertools.integrator.interfaces import IReadContainer, IFile, IImage
+
+
+# proxy base (sample) classes
+
+class ReadContainer(Contained):
+
+ implements(IReadContainer)
+
+ factoryName = 'sample'
+
+ def __init__(self, address, **kw):
+ self.address = address
+
+ @Lazy
+ def fileFactory(self):
+ return component.getUtility(IFileFactory, name=self.factoryName)
+
+ @Lazy
+ def containerFactory(self):
+ return component.getUtility(IContainerFactory, name=self.factoryName)
+
+ def keys(self):
+ return [k for k, v in self.items()]
+
+ def __iter__(self):
+ return iter(self.keys())
+
+ def __getitem__(self, key):
+ if key in self:
+ return self.get(key)
+ raise KeyError(key)
+
+ def get(self, key, default=None):
+ return default
+
+ def values(self):
+ return [v for k, v in self.items()]
+
+ def __len__(self):
+ return len(self.keys())
+
+ def items(self):
+ return []
+
+ def __contains__(self, key):
+ return key in self.keys()
+
+ has_key = __contains__
+
+
+class File(object):
+
+ implements(IFile)
+
+ contentType = None
+ data = None
+
+ def __init__(self, address, contentType, **kw):
+ self.address = address
+ self.contentType = contentType
+ for k, v in kw.items():
+ setattr(self, k, v)
+
+ def getData(self, num=None):
+ return ''
+
+ data = property(getData)
+
+ def getSize(self):
+ return len(self.data)
+
+
+def Image(File):
+
+ implements(IImage)
+
+ def getImageSize(self):
+ return 0, 0
+
+
+# factory base (sample) classes
+
+class Factory(object):
+
+ proxyClass = ReadContainer
+
+ def __call__(self, address, **kw):
+ return self.proxyClass(address, **kw)
+
+
+class ContainerFactory(Factory):
+
+ implements(IContainerFactory)
+
+ proxyClass = ReadContainer
+
+
+class FileFactory(Factory):
+
+ implements(IFileFactory)
+
+ proxyClass = File # real implementations should also care about images
+
diff --git a/integrator/filesystem.py b/integrator/filesystem.py
new file mode 100644
index 0000000..48934f6
--- /dev/null
+++ b/integrator/filesystem.py
@@ -0,0 +1,130 @@
+#
+# Copyright (c) 2008 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
+#
+
+"""
+Access to objects in the file system.
+
+$Id$
+"""
+
+import os, stat
+
+from zope import component
+from zope.app.file.image import getImageInfo
+from zope.cachedescriptors.property import Lazy
+from zope.contenttype import guess_content_type
+from zope.interface import implements, Attribute
+
+from cybertools.integrator.base import ContainerFactory, FileFactory
+from cybertools.integrator.base import ReadContainer, File, Image
+from cybertools.text import mimetypes
+
+
+# proxy classes
+
+class ReadContainer(ReadContainer):
+
+ factoryName = 'filesystem'
+
+ @Lazy
+ def filenames(self):
+ return os.listdir(self.address)
+
+ def keys(self):
+ return self.filenames
+
+ def __iter__(self):
+ return iter(self.keys())
+
+ def __getitem__(self, key):
+ if key in self:
+ return self.get(key)
+ raise KeyError(key)
+
+ def get(self, key, default=None):
+ if key not in self.keys():
+ return default
+ path = os.path.join(self.address, key)
+ if os.path.isdir(path):
+ return self.containerFactory(path)
+ else:
+ return self.fileFactory(path)
+
+ def values(self):
+ return [self.get(k) for k in self]
+
+ def __len__(self):
+ return len(self.keys())
+
+ def items(self):
+ return [(k, self.get(k)) for k in self]
+
+ def __contains__(self, key):
+ return key in self.keys()
+
+
+class File(File):
+
+ contentType = None
+ data = None
+
+ def getData(self, num=-1):
+ f = open(self.address, 'r')
+ data = f.read(num)
+ f.close()
+ return data
+
+ data = property(getData)
+
+ def getSize(self):
+ return os.stat(self.address)[stat.ST_SIZE]
+
+
+class Image(File):
+
+ width = height = 0
+
+ def getImageSize(self):
+ return self.width, self.height
+
+
+# factory classes
+
+class ContainerFactory(ContainerFactory):
+
+ proxyClass = ReadContainer
+
+
+class FileFactory(FileFactory):
+
+ def __call__(self, address, **kw):
+ contentType = kw.pop('contentType', None)
+ width = height = 0
+ obj = File(address, contentType, **kw)
+ if not contentType:
+ data = obj.getData(50)
+ contentType, width, height = getImageInfo(data)
+ if not contentType:
+ name = os.path.basename(address)
+ contentType, encoding = guess_content_type(name, data, '')
+ if contentType.startswith('image/'):
+ return Image(address, contentType=contentType,
+ width=width, height=height, **kw)
+ else:
+ obj.contentType = contentType
+ return obj
diff --git a/integrator/interfaces.py b/integrator/interfaces.py
new file mode 100644
index 0000000..1916eca
--- /dev/null
+++ b/integrator/interfaces.py
@@ -0,0 +1,59 @@
+#
+# Copyright (c) 2008 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
+#
+
+"""
+External content integration interfaces.
+
+$Id$
+"""
+
+from zope.app.container.interfaces import IReadContainer
+from zope.app.file.interfaces import IFile, IImage
+from zope.app.publication.interfaces import IFileContent
+from zope.interface import Interface, Attribute
+
+
+class IFile(IFile, IFileContent):
+
+ def getData(num):
+ """ Return num bytes from the file`s data.
+ """
+
+
+class IProxyFactory(Interface):
+ """ Creates proxy objects for external objects.
+ """
+
+ def __call__(address, **kw):
+ """ Return a proxy object based on an external object that
+ can be accessed using the address (and optional
+ keyword arguments) given.
+ """
+
+
+class IContainerFactory(IProxyFactory):
+ """ Creates container proxy objects for the external specification
+ given.
+ """
+
+
+class IFileFactory(IProxyFactory):
+ """ Creates file proxy objects for the external specification
+ given.
+ """
+
diff --git a/integrator/testdata/index.html b/integrator/testdata/index.html
new file mode 100644
index 0000000..8527580
--- /dev/null
+++ b/integrator/testdata/index.html
@@ -0,0 +1,9 @@
+
+