Index: main/waeup.sirp/trunk/src/waeup/sirp/tests/test_app.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/tests/test_app.py	(revision 6921)
+++ main/waeup.sirp/trunk/src/waeup/sirp/tests/test_app.py	(revision 7063)
@@ -6,5 +6,4 @@
 from zope.interface.verify import verifyClass, verifyObject
 from waeup.sirp.app import University
-from waeup.sirp.imagestorage import ImageStorageFileRetrieval
 from waeup.sirp.interfaces import IUniversity
 from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
@@ -32,9 +31,4 @@
         return
 
-    def test_images(self):
-        # Make sure we have a image container in a university
-        assert 'images' in self.app.keys()
-        return
-
     def test_IFileRetrieval_utility(self):
         # Make sure we can get a local IFileRetrieval utility
@@ -43,5 +37,4 @@
         assert result is not None
         assert IFileRetrieval.providedBy(result)
-        assert isinstance(result, ImageStorageFileRetrieval)
         return
 
Index: main/waeup.sirp/trunk/src/waeup/sirp/tests/test_imagestorage.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/tests/test_imagestorage.py	(revision 6921)
+++ main/waeup.sirp/trunk/src/waeup/sirp/tests/test_imagestorage.py	(revision 7063)
@@ -1,16 +1,20 @@
 import os
-import sys
 import tempfile
 import shutil
 import unittest
 from StringIO import StringIO
+from hurry.file import HurryFile
 from hurry.file.interfaces import IFileRetrieval
+from zope.component import (
+    getUtility, provideUtility, queryUtility, provideAdapter)
 from zope.component.hooks import setSite
 from zope.interface.verify import verifyClass, verifyObject
 from waeup.sirp.app import University
 from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
-from waeup.sirp.image import createWAeUPImageFile
 from waeup.sirp.imagestorage import (
-    md5digest, Basket, ImageStorage, ImageStorageFileRetrieval,)
+    FileStoreNameChooser, ExtFileStore, DefaultFileStoreHandler,
+    DefaultStorage)
+from waeup.sirp.interfaces import (
+    IFileStoreNameChooser, IExtFileStore, IFileStoreHandler,)
 
 class HelperFuncsTests(unittest.TestCase):
@@ -22,17 +26,145 @@
         shutil.rmtree(self.workdir)
 
-    def test_md5digest(self):
-        samplefile = os.path.join(self.workdir, 'sample')
-        open(samplefile, 'wb').write('blah')
-        fp = open(samplefile, 'r')
-        digest = md5digest(fp)
-        self.assertEqual(digest, '6f1ed002ab5595859014ebf0951522d9')
-
-class BasketTests(FunctionalTestCase):
+class FileStoreNameChooserTests(FunctionalTestCase):
 
     layer = FunctionalLayer
 
+    def test_iface(self):
+        # we provide the interfaces we promise to do
+        obj = FileStoreNameChooser(None)
+        verifyClass(IFileStoreNameChooser, FileStoreNameChooser)
+        verifyObject(IFileStoreNameChooser, obj)
+        return
+
+    def test_accessible_as_adapter(self):
+        # we can get a file name chooser via adapter
+        chooser = IFileStoreNameChooser(object())
+        self.assertTrue(
+            isinstance(chooser, FileStoreNameChooser))
+        return
+
+    def test_check_name(self):
+        # default file name choosers accept any string
+        chooser = FileStoreNameChooser(object())
+        self.assertEqual(chooser.checkName('Hi there!'), True)
+        self.assertEqual(chooser.checkName(None), False)
+        return
+
+    def test_choose_name(self):
+        # we get a simple string if we do not pass in a valid string
+        chooser = FileStoreNameChooser(object())
+        self.assertEqual(chooser.chooseName('myname'), 'myname')
+        self.assertEqual(chooser.chooseName(None), u'unknown_file')
+        return
+
+class ExtFileStoreTests(unittest.TestCase):
+    # Test external file store (non-functional mode)
+
     def setUp(self):
-        super(BasketTests, self).setUp()
+        self.workdir = tempfile.mkdtemp()
+        self.root = None
+        return
+
+    def tearDown(self):
+        shutil.rmtree(self.workdir)
+        if self.root is not None:
+            shutil.rmtree(self.root)
+        return
+
+    def test_iface(self):
+        obj = ExtFileStore(None)
+        verifyClass(IExtFileStore, ExtFileStore)
+        verifyObject(IExtFileStore, obj)
+        return
+
+    def test_root_setup_wo_site(self):
+        # if no site is available we can use a temporary root
+        fs = ExtFileStore()
+        self.root = fs.root
+        self.assertTrue(isinstance(self.root, basestring))
+        self.assertTrue(os.path.exists(self.root))
+        return
+
+    def test_create_instance(self):
+        storage1 = ExtFileStore()
+        storage2 = ExtFileStore(root=self.workdir)
+        self.root = storage1.root
+        self.assertTrue(storage1.root is not None)
+        self.assertTrue(storage1.root != storage2.root)
+        self.assertEqual(storage2.root, self.workdir)
+        return
+
+    def test_create_file(self):
+        # We can store files
+        storage = ExtFileStore(root=self.workdir)
+        dummy_file = StringIO('sample file')
+        image_file = storage.createFile('mysample', dummy_file)
+        self.assertTrue('mysample' in os.listdir(storage.root))
+        self.assertEqual('mysample', image_file.data)
+        return
+
+    def test_get_file(self):
+        # We can get files after having them stored
+        storage = ExtFileStore(root=self.workdir)
+        dummy_file = StringIO('sample file')
+        image_file = storage.createFile('mysample', dummy_file)
+        result = storage.getFile(image_file.data)
+        self.assertEqual(result.read(), 'sample file')
+        return
+
+    def test_extract_marker(self):
+        # file stores support extracting markers from filenames
+        storage = ExtFileStore(root=self.workdir)
+        result1 = storage.extractMarker(None)
+        result2 = storage.extractMarker('')
+        result3 = storage.extractMarker('no-marker')
+        result4 = storage.extractMarker('no-marker.txt')
+        result5 = storage.extractMarker('__MARKER__foo.jpg')
+        result6 = storage.extractMarker('__MaRkEr__foo.jpg')
+        result7 = storage.extractMarker('__THE_MARKER__foo.jpg')
+        result8 = storage.extractMarker('__A_MARK__my__foo.jpg')
+
+        self.assertEqual(result1, ('', '', '', ''))
+        self.assertEqual(result2, ('', '', '', ''))
+        self.assertEqual(result3, ('', 'no-marker', 'no-marker', ''))
+        self.assertEqual(result4, ('', 'no-marker.txt', 'no-marker', '.txt'))
+        self.assertEqual(result5, ('marker', 'foo.jpg', 'foo', '.jpg'))
+        self.assertEqual(result6, ('marker', 'foo.jpg', 'foo', '.jpg'))
+        self.assertEqual(result7, ('the_marker', 'foo.jpg', 'foo', '.jpg'))
+        self.assertEqual(result8, ('a_mark', 'my__foo.jpg', 'my__foo', '.jpg'))
+        return
+
+class DefaultFileStoreHandlerTests(unittest.TestCase):
+
+    def test_iface(self):
+        obj = DefaultFileStoreHandler()
+        verifyClass(IFileStoreHandler, DefaultFileStoreHandler)
+        verifyObject(IFileStoreHandler, obj)
+        return
+
+class CustomizedFileHandler(object):
+    def pathFromFileID(self, store, root, file_id):
+        return os.path.join(root, file_id[12:])
+
+    def createFile(self, store, root, file_id, filename, f):
+        path = self.pathFromFileID(store, root, file_id)
+        return f, path, HurryFile(filename, file_id)
+
+class CustomContext(object):
+    pass
+
+class CustomContextFileChooser(object):
+    def __init__(self, context):
+        self.context = context
+
+    def chooseName(self, name=None):
+        return '__mymarker__mysample.txt'
+
+class FunctionalExtFileStoreTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(FunctionalExtFileStoreTests, self).setUp()
         self.workdir = tempfile.mkdtemp()
         self.samplefile = os.path.join(self.workdir, 'sample')
@@ -40,263 +172,110 @@
         open(self.samplefile, 'wb').write('Hi there!')
         open(self.otherfile, 'wb').write('Hi from other!')
-        self.basket = Basket()
         self.fd = open(self.samplefile, 'r')
         self.fd2 = open(self.otherfile, 'r')
-        self.stderr = StringIO()
-        self.old_stderr = sys.stderr
+        self.getRootFolder()['app'] = University()
+        self.app = self.getRootFolder()['app']
+        self.app['datacenter'].setStoragePath(self.workdir)
+        # register a custom filename mangler
+        provideUtility(
+            CustomizedFileHandler(), IFileStoreHandler, name=u'mymarker')
+        # register a file chooser adapter for CustomContext
+        provideAdapter(
+            CustomContextFileChooser,
+            (CustomContext,), IFileStoreNameChooser)
+        return
+
 
     def tearDown(self):
-        sys.stderr = self.old_stderr
-        super(BasketTests, self).tearDown()
-        self.fd.close()
-        shutil.rmtree(self.workdir)
-        self.basket._del() # Remove subojects explicitly
-        del self.basket
-        return
-
-    def test_ifaces(self):
-        pass
-
-    def test_curr_id_empty(self):
-        curr_id = self.basket.curr_id
-        self.assertEqual(curr_id, '1')
-
-    def test_getInternalId_empty(self):
-        basket_id = self.basket.getInternalId(self.fd)
-        self.assertTrue(basket_id is None)
-
-    def test_storeFile_single(self):
-        basket_id = self.basket.storeFile(self.fd, 'sample')
-        self.assertEqual(basket_id, '1')
-        contents = self.basket['1'].open('r').read()
-        self.assertEqual(contents, 'Hi there!')
-
-    def test_storeFile_double(self):
-        basket_id1 = self.basket.storeFile(self.fd, 'sample')
-        basket_id2 = self.basket.storeFile(self.fd, 'sample')
-        self.assertTrue(basket_id1 == basket_id2 == '1')
-        contents = self.basket['1'].open('r').read()
-        self.assertEqual(contents, 'Hi there!')
-
-    def test_storeFile_multiple(self):
-        basket_id1 = self.basket.storeFile(self.fd, 'sample')
-        basket_id2 = self.basket.storeFile(self.fd2, 'sample')
-        self.assertEqual(basket_id1, '1')
-        self.assertEqual(basket_id2, '2')
-        contents1 = self.basket['1'].open('r').read()
-        contents2 = self.basket['2'].open('r').read()
-        self.assertEqual(contents1, 'Hi there!')
-        self.assertEqual(contents2, 'Hi from other!')
-
-    def test_retrieveFile(self):
-        basket_id = self.basket.storeFile(self.fd, 'sample')
-        fd = self.basket.retrieveFile(basket_id)
-        result = fd.read()
-        self.assertEqual(result, 'Hi there!')
-
-    def test_retrieveFile_not_existent(self):
-        result = self.basket.retrieveFile('not-a-valid-basket-id')
-        self.assertTrue(result is None)
-
-    def test_detect_zero_length_blobs(self):
-        # Ensure we get a warning when an empty Blob is found
-        self.basket.storeFile(self.fd, 'sample')
-        self.basket['1'].open('w').write('')
-        self.fd.seek(0)
-        sys.stderr = self.stderr         # Redirect stderr
-        self.basket.storeFile(self.fd, 'sample')
-        sys.stderr = self.old_stderr     # Restore stderr
-        self.stderr.seek(0)
-        self.assertTrue(
-            "EMPTY BLOB DETECTED" in self.stderr.read())
-
-    def test_refill_zero_length_blobs(self):
-        # When we detect an empty Blob, it will be reused
-        self.basket.storeFile(self.fd, 'sample')
-        self.basket['1'].open('w').write('')
-        self.fd.seek(0)
-        sys.stderr = self.stderr         # Redirect stderr
-        self.basket.storeFile(self.fd, 'sample')
-        sys.stderr = self.old_stderr     # Restore stderr
-        contents = self.basket['1'].open('r').read()
-        self.assertEqual(contents, 'Hi there!')
-
-
-class ImageStorageTests(FunctionalTestCase):
-
-    layer = FunctionalLayer
-
-    def setUp(self):
-        super(ImageStorageTests, self).setUp()
-        self.workdir = tempfile.mkdtemp()
-        self.samplefile = os.path.join(self.workdir, 'sample')
-        self.otherfile = os.path.join(self.workdir, 'other')
-        open(self.samplefile, 'wb').write('Hi there!')
-        open(self.otherfile, 'wb').write('Hi from other!')
-        self.storage = ImageStorage()
-        self.fd = open(self.samplefile, 'r')
-        self.fd2 = open(self.otherfile, 'r')
-
-    def tearDown(self):
-        super(ImageStorageTests, self).tearDown()
+        super(FunctionalExtFileStoreTests, self).tearDown()
         self.fd.close()
         self.fd2.close()
         shutil.rmtree(self.workdir)
-        self.storage._del() # Remove subojects explicitly
-        del self.storage
-        return
-
-    def test_ifaces(self):
-        pass
-
-    def test_storeFile(self):
-        full_id = self.storage.storeFile(self.fd, 'sample.txt')
-        self.assertEqual(full_id, '396199333edbf40ad43e62a1c1397793-1')
-
-    def test_storeFile_duplicate(self):
-        full_id1 = self.storage.storeFile(self.fd, 'sample1.txt')
-        full_id2 = self.storage.storeFile(self.fd, 'sample2.txt')
-        full_id3 = self.storage.storeFile(self.fd, 'sample1.txt')
-        self.assertEqual(full_id1, '396199333edbf40ad43e62a1c1397793-1')
-        self.assertEqual(full_id2, '396199333edbf40ad43e62a1c1397793-1')
-        self.assertEqual(full_id3, '396199333edbf40ad43e62a1c1397793-1')
-        contents = self.storage.retrieveFile(
-            '396199333edbf40ad43e62a1c1397793-1').read()
-        self.assertEqual(contents, 'Hi there!')
-
-    def test_storeFile_multiple(self):
-        full_id1 = self.storage.storeFile(self.fd, 'sample1.txt')
-        full_id2 = self.storage.storeFile(self.fd2, 'sample1.txt')
-        self.assertEqual(full_id1, '396199333edbf40ad43e62a1c1397793-1')
-        self.assertEqual(full_id2, '6936fcf8d564f1c5be5a017e650c5e8f-1')
-
-    def test_retrieveFile_not_existent(self):
-        result = self.storage.retrieveFile('not-existent')
-        self.assertTrue(result is None)
-
-    def test_retrieveFile_illegal_marker(self):
-        result1 = self.storage.retrieveFile('really-not-existent')
-        result2 = self.storage.retrieveFile('notexistent')
-        self.assertTrue(result1 is result2 is None)
-
-    def test_retrieveFile(self):
-        full_id = self.storage.storeFile(self.fd, 'sample.txt')
-        result = self.storage.retrieveFile(full_id)
-        content = result.read()
-        self.assertEqual(content, 'Hi there!')
-
-    def test_retrieveFile_multiple(self):
-        full_id1 = self.storage.storeFile(self.fd, 'sample.txt')
-        full_id2 = self.storage.storeFile(self.fd2, 'other.txt')
-        result1 = self.storage.retrieveFile(full_id1)
-        result2 = self.storage.retrieveFile(full_id2)
-        content1 = result1.read()
-        content2 = result2.read()
-        self.assertEqual(content1, 'Hi there!')
-        self.assertEqual(content2, 'Hi from other!')
-
-class ImageStorageFileRetrievalTests(FunctionalTestCase):
-
-    layer = FunctionalLayer
-
-    def setUp(self):
-        super(ImageStorageFileRetrievalTests, self).setUp()
-        self.workdir = tempfile.mkdtemp()
-        self.samplefile = os.path.join(self.workdir, 'sample')
-        self.otherfile = os.path.join(self.workdir, 'other')
-        open(self.samplefile, 'wb').write('Hi there!')
-        open(self.otherfile, 'wb').write('Hi from other!')
-        self.storage = ImageStorage()
-        self.fd = open(self.samplefile, 'r')
-        self.fd2 = open(self.otherfile, 'r')
-        # Set up a single image storage in a site
-        self.getRootFolder()['app'] = University()
-        self.app = self.getRootFolder()['app']
-        if not 'images' in self.app.keys():
-            self.app['images'] = ImageStorage()
-        self.storage = self.app['images']
-        return
-
-
-    def tearDown(self):
-        super(ImageStorageFileRetrievalTests, self).tearDown()
-        self.fd.close()
-        self.fd2.close()
-        shutil.rmtree(self.workdir)
-        self.storage._del() # Remove subojects explicitly
-        return
-
-    def test_ifaces(self):
-        retrieval = ImageStorageFileRetrieval()
-        assert verifyClass(IFileRetrieval, ImageStorageFileRetrieval)
-        assert verifyObject(IFileRetrieval, retrieval)
-        return
-
-    def test_getImageStorage_nosite(self):
-        retrieval = ImageStorageFileRetrieval()
-        storage = retrieval.getImageStorage()
-        self.assertTrue(storage is None)
-        return
-
-    def test_getImageStorage(self):
+        return
+
+    def test_root_setup_w_site(self):
+        # if a site is available we use it to determine the root dir
+        fs = ExtFileStore()
         setSite(self.app)
-        retrieval = ImageStorageFileRetrieval()
-        storage = retrieval.getImageStorage()
-        self.assertTrue(storage is self.storage)
-        return
-
-    def test_isImageStorageEnabled_nosite(self):
-        retrieval = ImageStorageFileRetrieval()
-        self.assertTrue(retrieval.isImageStorageEnabled() is False)
-        return
-
-    def test_isImageStorageEnabled(self):
-        setSite(self.app)
-        retrieval = ImageStorageFileRetrieval()
-        self.assertTrue(retrieval.isImageStorageEnabled() is True)
-        return
-
-    def test_getFile_nosite(self):
-        retrieval = ImageStorageFileRetrieval()
-        f = retrieval.getFile('Hi there!')
-        self.assertEqual(f.read(), 'Hi there!')
-        return
-
-    def test_getFile(self):
-        setSite(self.app)
-        retrieval = ImageStorageFileRetrieval()
-        waeup_image = retrieval.createFile('sample.txt', self.fd)
-        full_id = waeup_image.data
-        result = retrieval.getFile(full_id)
-        self.assertEqual(result.read(), 'Hi there!')
-        return
-
-    def test_createFile_nosite(self):
-        retrieval = ImageStorageFileRetrieval()
-        waeup_image = retrieval.createFile('sample.txt', self.fd)
-        self.assertEqual(waeup_image.data, 'Hi there!')
-        return
-
-    def test_createFile(self):
-        # Ensure we can create WAeUPImageFiles when in site
-        setSite(self.app)
-        retrieval = ImageStorageFileRetrieval()
-        waeup_image = retrieval.createFile('sample.txt', self.fd)
-        full_id = waeup_image.data
-        self.assertEqual(full_id, '396199333edbf40ad43e62a1c1397793-1')
-        return
-
-    def test_waeupimagefile(self):
-        # Make sure WAeUPImageFile can use our file retrieval
-        setSite(self.app)
-        myfile = createWAeUPImageFile('sample.jpg', self.fd)
-        contents = myfile.file.read()
-        self.assertEqual(contents, 'Hi there!')
-
-    def test_waeupimagefile_raw(self):
-        # Make sure we can retrieve a file also if it was initialized
-        # with no image storage available
-        myfile = createWAeUPImageFile('sample.jpg', self.fd)
-        setSite(self.app)
-        contents = myfile.file.read()
-        self.assertEqual(contents, 'Hi there!')
+        self.root = fs.root
+        expected_root = os.path.join(
+            self.app['datacenter'].storage, 'media')
+        self.assertTrue(isinstance(self.root, basestring))
+        self.assertEqual(self.root, expected_root)
+        return
+
+    def test_get_utility(self):
+        # we can get an ExtFileStore by global utility lookup
+        fs1 = getUtility(IExtFileStore)
+        fs2 = getUtility(IExtFileStore)
+        self.assertTrue(isinstance(fs1, ExtFileStore))
+        self.assertTrue(fs1 is fs2)
+        return
+
+    def test_default_handler_create_file(self):
+        # we can use the default handler to store files
+        fs = ExtFileStore()
+        result = fs.createFile('sample.txt', StringIO('sample text'))
+        self.assertEqual(result.data, 'sample.txt')
+        self.assertTrue('sample.txt' in os.listdir(fs.root))
+        return
+
+    def test_default_handler_get_file(self):
+        # we can get files stored by the default handler
+        fs = ExtFileStore()
+        fs.createFile('sample.txt', StringIO('sample text'))
+        result1 = fs.getFile('sample.txt')
+        result2 = fs.getFile('not-existent')
+        self.assertEqual(result1.read(), 'sample text')
+        self.assertTrue(result2 is None)
+        return
+
+    def test_customized_handler_create_file(self):
+        # we can use registered filename handlers
+        fs = ExtFileStore()
+        result = fs.createFile(
+            '__MYMARKER__sample.txt', StringIO('sample text'))
+        self.assertEqual(result.data, '__MYMARKER__sample.txt')
+        self.assertTrue('sample.txt' in os.listdir(fs.root))
+        return
+
+    def test_customized_handler_get_file(self):
+        # we consider registered filename handlers when asking for
+        # stored files.
+        fs = ExtFileStore()
+        fs.createFile('__MYMARKER__sample.txt', StringIO('sample text'))
+        result1 = fs.getFile('__MYMARKER__sample.txt')
+        result2 = fs.getFile('__MYMARKER__not-existent')
+        result3 = fs.getFile('not-existent')
+        self.assertEqual(result1.read(), 'sample text')
+        self.assertTrue(result2 is None)
+        self.assertTrue(result3 is None)
+        return
+
+    def test_get_file_by_context(self):
+        # if we register a file name chooser, we can also get a file
+        # by context
+        fs = ExtFileStore()
+        context = CustomContext()
+        file_id = IFileStoreNameChooser(context).chooseName()
+        fs = ExtFileStore()
+        fs.createFile(file_id, StringIO('my sample'))
+        result = fs.getFileByContext(context)
+        self.assertEqual(
+            file_id, '__mymarker__mysample.txt')
+        self.assertEqual(
+            result.read(), 'my sample')
+        return
+
+    def test_get_default_handler(self):
+        # we can get a default handler
+        result = queryUtility(IFileStoreHandler)
+        self.assertTrue(
+            isinstance(result, DefaultFileStoreHandler))
+        return
+
+    def test_get_default_file_retrieval(self):
+        # we get a file store when requesting a file retrieval
+        result = queryUtility(IFileRetrieval)
+        self.assertTrue(
+            isinstance(result, DefaultStorage))
