Changeset 7010


Ignore:
Timestamp:
6 Nov 2011, 23:36:21 (13 years ago)
Author:
uli
Message:
  • Give an overview over file handling with the external file store.
  • Minor fixes.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • main/waeup.sirp/branches/ulif-extimgstore/src/waeup/sirp/imagestorage.py

    r7002 r7010  
    2121##
    2222"""A storage for image files.
     23
     24A few words about storing files with ``waeup.sirp``. The need for this
     25feature arised initially from the need to store passport files for
     26applicants and students. These files are dynamic (can be changed
     27anytime), mean a lot of traffic and cost a lot of memory/disk space.
     28
     29**Design Basics**
     30
     31While one *can* store images and similar 'large binary objects' aka
     32blobs in the ZODB, this approach quickly becomes cumbersome and
     33difficult to understand. The worst approach here would be to store
     34images as regular byte-stream objects. ZODB supports this but
     35obviously access is slow (data must be looked up in the one
     36``Data.fs`` file, each file has to be sent to the ZEO server and back,
     37etc.).
     38
     39A bit less worse is the approach to store images in the ZODB but as
     40Blobs. ZODB supports storing blobs in separate files in order to
     41accelerate lookup/retrieval of these files. The files, however, have
     42to be sent to the ZEO server (and back on lookups) which means a
     43bottleneck and will easily result in an increased number of
     44``ConflictErrors`` even on simple reads.
     45
     46The advantage of both ZODB-geared approaches is, of course, complete
     47database consistency. ZODB will guarantee that your files are
     48available under some object name and can be handled as any other
     49Python object.
     50
     51Another approach is to leave the ZODB behind and to store images and
     52other files in filesystem directly. This is faster (no ZEO contacts,
     53etc.), reduces probability of `ConflictErrors`, keeps the ZODB
     54smaller, and enables direct access (over filesystem) to the
     55files. Furthermore steps might be better understandable for
     56third-party developers. We opted for this last option.
     57
     58**External File Store**
     59
     60Our implementation for storing-files-API is defined in
     61:class:`ExtFileStore`. An instance of this file storage (which is also
     62able to store non-image files) is available at runtime as a global
     63utility implementing :class:`waeup.sirp.interfaces.IExtFileStore`.
     64
     65The main task of this central component is to maintain a filesystem
     66root path for all files to be stored. It also provides methods to
     67store/get files under certain file ids which identify certain files
     68locally.
     69
     70So, to store a file away, you can do something like this:
     71
     72  >>> from StringIO import StringIO
     73  >>> from zope.component import getUtility
     74  >>> from waeup.sirp.interfaces import IExtFileStore
     75  >>> store = getUtility(IExtFileStore)
     76  >>> store.createFile('myfile.txt', StringIO('some file content'))
     77
     78All you need is a filename and the file-like object containing the
     79real file data.
     80
     81This will store the file somewhere (you shouldn't make too much
     82assumptions about the real filesystem path here).
     83
     84Later, we can get the file back like this:
     85
     86  >>> store.getFile('myfile.txt')
     87  <open file ...>
     88
     89What we get back is a file or file-like object already opened for
     90reading:
     91
     92  >>> store.getFile('myfile.txt').read()
     93  'some file content'
     94
     95**Handlers: Special Places for Special Files**
     96
     97The file store supports special handling for certain files. For
     98example we want applicant images to be stored in a different directory
     99than student images, etc. Because the file store cannot know all
     100details about these special tratment of certain files, it looks up
     101helpers (handlers) to provide the information it needs for really
     102storing the files at the correct location.
     103
     104That a file stored in filestore needs special handling can be
     105indicated by special filenames. These filenames start with a marker like
     106this::
     107
     108  __<MARKER-STRING>__real-filename.jpg
     109
     110Please note the double underscores before and after the marker
     111string. They indicate that all in between is a marker.
     112
     113If you store a file in file store with such a filename (we call this a
     114`file_id` to distuingish it from real world filenames), the file store
     115will look up a handler for ``<MARKER-STRING>`` and pass it the file to
     116store. The handler then will return the internal path to store the
     117file and possibly do additional things as well like validating the
     118file or similar.
     119
     120Examples for such a file store handler can be found in the
     121:mod:`waeup.sirp.applicants.applicant` module. Please see also the
     122:class:`DefaultFileStoreHandler` class below for more details.
     123
     124The file store looks up handlers by utility lookups: it looks for a
     125named utiliy providing
     126:class:`waeup.sirp.interfaces.IFileStoreHandler` and named like the
     127marker string (without leading/trailing underscores) in lower
     128case. For example if the file id would be
     129
     130  ``__IMG_USER__manfred.jpg``
     131
     132then the looked up utility should be registered under name
     133
     134  ``img_user``
     135
     136and provide :class:`waeup.sirp.interfaces.IFileStoreHandler`. If no
     137such utility can be found, a default handler is used instead
     138(see :class:`DefaultFileStoreHandler`).
     139
     140**Context Adapters: Knowing Your Family**
     141
     142Often the internal filename or file id of a file depends on a
     143context. For example when we store passport photographs of applicants,
     144then each image belongs to a certain applicant instance. It is not
     145difficult to maintain such a connection manually: Say every applicant
     146had an id, then we could put this id into the filename as well and
     147would build the filename to store/get the connected file by using that
     148filename. You then would create filenames of a format like this::
     149
     150  __<MARKER-STRING>__applicant0001.jpg
     151
     152where ``applicant0001`` would tell exactly which applicant you can see
     153on the photograph. You notice that the internal file id might have
     154nothing to do with once uploaded filenames. The id above could have
     155been uploaded with filename ``manfred.jpg`` but with the new file id
     156we are able to find the file again later.
     157
     158Unfortunately it might soon get boring or cumbersome to retype this
     159building of filenames for a certain type of context, especially if
     160your filenames take more of the context into account than only a
     161simple id.
     162
     163Therefore you can define filename building for a context as an adapter
     164that then could be looked up by other components simply by doing
     165something like:
     166
     167  >>> from waeup.sirp.interfaces import IFileStoreNameChooser
     168  >>> file_id = IFileStoreNameChooser(my_context_obj)
     169
     170If you later want to change the way file ids are created from a
     171certain context, you only have to change the adapter implementation
     172accordingly.
     173
     174Note, that this is only a convenience component. You don't have to
     175define context adapters but it makes things easier for others if you
     176do, as you don't have to remember the exact file id creation method
     177all the time and can change things quick and in only one location if
     178you need to do so.
     179
     180Please see the :class:`FileStoreNameChooser` default implementation
     181below for details.
     182
    23183"""
    24184import grok
     
    180340class ImageStorage(grok.Container):
    181341    """A container for image files.
     342
     343    .. deprecated:: 0.2
     344
     345       Use :class:`waeup.sirp.ExtFileStore` instead.
    182346    """
    183347    def _del(self):
     
    246410
    247411
    248 class ExtFileStore(object): #grok.GlobalUtility):
     412class ExtFileStore(object):
    249413    """External file store.
    250414
     
    408572    def createFile(self, store, root, filename, file_id, f):
    409573        path = self.pathFromFileID(store, root, file_id)
    410         return f, path, WAeUPImageFile(filename, file_id)
    411         return path, HurryFile(filename, file_id)
     574        return f, path, HurryFile(filename, file_id)
Note: See TracChangeset for help on using the changeset viewer.