class NamedBlobFile(Persistent): """A file stored in a ZODB BLOB, with a filename""" filename = FieldProperty(INamedFile['filename']) def __init__(self, data='', contentType='', filename=None): if ( filename is not None and contentType in ('', 'application/octet-stream') ): contentType = get_contenttype(filename=filename) self.contentType = contentType self._blob = Blob() f = self._blob.open('w') f.write('') f.close() self._setData(data) self.filename = filename def open(self, mode='r'): if mode != 'r' and 'size' in self.__dict__: del self.__dict__['size'] return self._blob.open(mode) def openDetached(self): return open(self._blob.committed(), 'rb') def _setData(self, data): if 'size' in self.__dict__: del self.__dict__['size'] # Search for a storable that is able to store the data dottedName = '.'.join((data.__class__.__module__, data.__class__.__name__)) log.debug('Storage selected for data: %s', dottedName) storable = getUtility(IStorage, name=dottedName) storable.store(data, self._blob) def _getData(self): fp = self._blob.open('r') data = fp.read() fp.close() return data _data = property(_getData, _setData) data = property(_getData, _setData) @property def size(self): if 'size' in self.__dict__: return self.__dict__['size'] reader = self._blob.open() reader.seek(0, 2) size = int(reader.tell()) reader.close() self.__dict__['size'] = size return size def getSize(self): return self.size
class NamedBlobFile(Persistent): """A file stored in a ZODB BLOB, with a filename""" filename = FieldProperty(INamedFile['filename']) def __init__(self, data=b'', contentType='', filename=None): if ( filename is not None and contentType in ('', 'application/octet-stream') ): contentType = get_contenttype(filename=filename) self.contentType = contentType self._blob = Blob() f = self._blob.open('w') f.write(b'') f.close() self._setData(data) self.filename = filename def open(self, mode='r'): if mode != 'r' and 'size' in self.__dict__: del self.__dict__['size'] return self._blob.open(mode) def openDetached(self): return open(self._blob.committed(), 'rb') def _setData(self, data): if 'size' in self.__dict__: del self.__dict__['size'] # Search for a storable that is able to store the data dottedName = '.'.join((data.__class__.__module__, data.__class__.__name__)) log.debug('Storage selected for data: %s', dottedName) storable = getUtility(IStorage, name=dottedName) storable.store(data, self._blob) def _getData(self): fp = self._blob.open('r') data = fp.read() fp.close() return data _data = property(_getData, _setData) data = property(_getData, _setData) @property def size(self): if 'size' in self.__dict__: return self.__dict__['size'] with self._blob.open() as reader: reader.seek(0, 2) size = int(reader.tell()) self.__dict__['size'] = size return size def getSize(self): return self.size
class NamedBlobFile(Persistent): """A file stored in a ZODB BLOB, with a filename""" implements(INamedBlobFile) filename = FieldProperty(INamedFile["filename"]) def __init__(self, data="", contentType="", filename=None): if filename is not None and contentType in ("", "application/octet-stream"): contentType = get_contenttype(filename=filename) self.contentType = contentType self._blob = Blob() f = self._blob.open("w") f.write("") f.close() self._setData(data) self.filename = filename def open(self, mode="r"): if mode != "r" and "size" in self.__dict__: del self.__dict__["size"] return self._blob.open(mode) def openDetached(self): return open(self._blob.committed(), "rb") def _setData(self, data): if "size" in self.__dict__: del self.__dict__["size"] # Search for a storable that is able to store the data dottedName = ".".join((data.__class__.__module__, data.__class__.__name__)) storable = getUtility(IStorage, name=dottedName) storable.store(data, self._blob) def _getData(self): fp = self._blob.open("r") data = fp.read() fp.close() return data _data = property(_getData, _setData) data = property(_getData, _setData) @property def size(self): if "size" in self.__dict__: return self.__dict__["size"] reader = self._blob.open() reader.seek(0, 2) size = int(reader.tell()) reader.close() self.__dict__["size"] = size return size def getSize(self): return self.size
class File(Persistent): """A persistent content component storing binary file data.""" implements(zope.app.publication.interfaces.IFileContent, interfaces.IBlobFile) def __init__(self, data='', contentType=''): self.contentType = contentType self._blob = Blob() f = self._blob.open('w') f.write('') f.close() self._setData(data) def open(self, mode='r'): if mode != 'r' and 'size' in self.__dict__: del self.__dict__['size'] return self._blob.open(mode) def openDetached(self): return open(self._blob.committed(), 'rb') def _setData(self, data): if 'size' in self.__dict__: del self.__dict__['size'] # Search for a storable that is able to store the data dottedName = ".".join((data.__class__.__module__, data.__class__.__name__)) storable = zope.component.getUtility(interfaces.IStorage, name=dottedName) storable.store(data, self._blob) def _getData(self): fp = self._blob.open('r') data = fp.read() fp.close() return data _data = property(_getData, _setData) data = property(_getData, _setData) @property def size(self): if 'size' in self.__dict__: return self.__dict__['size'] reader = self._blob.open() reader.seek(0,2) size = int(reader.tell()) reader.close() self.__dict__['size'] = size return size def getSize(self): return self.size
def test_committed_when_uncommitted(self): # An exception is raised if we call committed on a blob that has # uncommitted changes: blob = Blob() with self.assertRaisesRegex(BlobError, "Uncommitted changes"): blob.committed() with self.assertRaisesRegex(BlobError, "Uncommitted changes"): blob.open('c') blob = self._make_blob() conn = self.database.open() root = conn.root() root['blob'] = blob with self.assertRaisesRegex(BlobError, "Uncommitted changes"): blob.committed() with self.assertRaisesRegex(BlobError, "Uncommitted changes"): blob.open('c') transaction.savepoint() with self.assertRaisesRegex(BlobError, "Uncommitted changes"): blob.committed() with self.assertRaisesRegex(BlobError, "Uncommitted changes"): blob.open('c') transaction.commit() self._check_blob_contents(blob, self.DATA1, 'c')
class File(Persistent): title = _BLANK name = renamer() def __init__(self, stream=None, mimetype=None, title=_BLANK): """ The constructor of a File object. ``stream`` should be a filelike object (an object with a ``read`` method that takes a size argument) or ``None``. If stream is ``None``, the blob attached to this file object is created empty. ``title`` must be a string or Unicode object. ``mimetype`` may be any of the following: - ``None``, meaning set this file object's mimetype to ``application/octet-stream`` (the default). - A mimetype string (e.g. ``image/gif``) - The constant :attr:`substanced.file.USE_MAGIC`, which will derive the mimetype from the stream content (if ``stream`` is also supplied) using the ``python-magic`` library. .. warning:: On non-Linux systems, successful use of :attr:`substanced.file.USE_MAGIC` requires the installation of additional dependencies. See :ref:`optional_dependencies`. """ self.blob = Blob() self.title = title or _BLANK # mimetype will be overridden by upload if there's a stream if mimetype is USE_MAGIC: self.mimetype = "application/octet-stream" else: self.mimetype = mimetype or "application/octet-stream" if stream is not None: if mimetype is USE_MAGIC: hint = USE_MAGIC else: hint = None self.upload(stream, mimetype_hint=hint) def upload(self, stream, mimetype_hint=None): """ Replace the current contents of this file's blob with the contents of ``stream``. ``stream`` must be a filelike object (it must have a ``read`` method that takes a size argument). ``mimetype_hint`` can be any of the following: - ``None``, meaning don't reset the current mimetype. This is the default. If you already know the file's mimetype, and you don't want it divined from a filename or stream content, use ``None`` as the ``mimetype_hint`` value, and set the ``mimetype`` attribute of the file object directly before or after calling this method. - A string containing a filename that has an extension; the mimetype will be derived from the extension in the filename using the Python ``mimetypes`` module, and the result will be set as the mimetype attribute of this object. - The constant :attr:`pyramid.file.USE_MAGIC`, which will derive the mimetype using the ``python-magic`` library based on the stream's actual content. The result will be set as the mimetype attribute of this object. .. warning:: On non-Linux systems, successful use of :attr:`substanced.file.USE_MAGIC` requires the installation of additional dependencies. See :ref:`optional_dependencies`. """ if not stream: stream = io.StringIO() fp = self.blob.open("w") first = True use_magic = False if mimetype_hint is USE_MAGIC: use_magic = True if magic is None: # pragma: no cover warnings.warn( "The python-magic library does not have its requisite " "dependencies installed properly, therefore the " '"USE_MAGIC" flag passed to this method has been ignored ' '(it has been converted to "None"). The mimetype of ' "substanced.file.File objects created may be incorrect as " "a result." ) use_magic = False mimetype_hint = None if not use_magic: if mimetype_hint is not None: mimetype, _ = mimetypes.guess_type(mimetype_hint, strict=False) if mimetype is None: mimetype = "application/octet-stream" self.mimetype = mimetype for chunk in chunks(stream): if use_magic and first: first = False m = magic.Magic(mime=True) mimetype = m.from_buffer(chunk) self.mimetype = u(mimetype) fp.write(chunk) fp.close() def get_response(self, **kw): """ Return a WebOb-compatible response object which uses the blob content as the stream data and the mimetype of the file as the content type. The ``**kw`` arguments will be passed to the ``pyramid.response.FileResponse`` constructor as its keyword arguments.""" if not "content_type" in kw: kw["content_type"] = str(self.mimetype) path = self.blob.committed() response = FileResponse(path, **kw) return response def get_size(self): """ Return the size in bytes of the data in the blob associated with the file""" return os.stat(self.blob.committed()).st_size def get_etag(self): """ Return a token identifying the "version" of the file. """ self._p_activate() mine = self._p_serial blob = self.blob._p_serial if blob == z64: self.blob._p_activate() blob = self.blob._p_serial return oid_repr(max(mine, blob))
class File(Persistent): title = u'' def __init__(self, stream=None, mimetype=None, title=u''): """ The constructor of a File object. ``stream`` should be a filelike object (an object with a ``read`` method that takes a size argument) or ``None``. If stream is ``None``, the blob attached to this file object is created empty. ``title`` must be a string or Unicode object. ``mimetype`` may be any of the following: - ``None``, meaning set this file object's mimetype to ``application/octet-stream`` (the default). - A mimetype string (e.g. ``image/gif``) - The constant :attr:`substanced.file.USE_MAGIC`, which will derive the mimetype from the stream content (if ``stream`` is also supplied) using the ``python-magic`` library. .. warning:: On non-Linux systems, successful use of :attr:`substanced.file.USE_MAGIC` requires the installation of additional dependencies. See :ref:`optional_dependencies`. """ self.blob = Blob() self.mimetype = mimetype or 'application/octet-stream' self.title = title or u'' if stream is not None: if mimetype is USE_MAGIC: hint = USE_MAGIC else: hint = None self.upload(stream, mimetype_hint=hint) def upload(self, stream, mimetype_hint=None): """ Replace the current contents of this file's blob with the contents of ``stream``. ``stream`` must be a filelike object (it must have a ``read`` method that takes a size argument). ``mimetype_hint`` can be any of the following: - ``None``, meaning don't reset the current mimetype. This is the default. If you already know the file's mimetype, and you don't want it divined from a filename or stream content, use ``None`` as the ``mimetype_hint`` value, and set the ``mimetype`` attribute of the file object directly before or after calling this method. - A string containing a filename that has an extension; the mimetype will be derived from the extension in the filename using the Python ``mimetypes`` module, and the result will be set as the mimetype attribute of this object. - The constant :attr:`pyramid.file.USE_MAGIC`, which will derive the mimetype using the ``python-magic`` library based on the stream's actual content. The result will be set as the mimetype attribute of this object. .. warning:: On non-Linux systems, successful use of :attr:`substanced.file.USE_MAGIC` requires the installation of additional dependencies. See :ref:`optional_dependencies`. """ if not stream: stream = StringIO.StringIO() fp = self.blob.open('w') first = True use_magic = False if mimetype_hint is USE_MAGIC: use_magic = True if magic is None: # pragma: no cover warnings.warn( 'The python-magic library does not have its requisite ' 'dependencies installed properly, therefore the ' '"USE_MAGIC" flag passed to this method has been ignored ' '(it has been converted to "None"). The mimetype of ' 'substanced.file.File objects created may be incorrect as ' 'a result.') use_magic = False mimetype_hint = None if not use_magic: if mimetype_hint is not None: mimetype, _ = mimetypes.guess_type(mimetype_hint, strict=False) if mimetype is None: mimetype = 'application/octet-stream' self.mimetype = mimetype for chunk in chunks(stream): if use_magic and first: first = False m = magic.Magic(mime=True) mimetype = m.from_buffer(chunk) self.mimetype = mimetype fp.write(chunk) fp.close() def get_response(self, **kw): """ Return a WebOb-compatible response object which uses the blob content as the stream data and the mimetype of the file as the content type. The ``**kw`` arguments will be passed to the ``pyramid.response.FileResponse`` constructor as its keyword arguments.""" if not 'content_type' in kw: kw['content_type'] = self.mimetype path = self.blob.committed() response = FileResponse(path, **kw) return response def get_size(self): """ Return the size in bytes of the data in the blob associated with the file""" return os.stat(self.blob.committed()).st_size
class NyBlobFile(Persistent): """Naaya container for files stored using ZODB Blob""" implements(INyBlobFile) def __init__(self, **kwargs): kwargs.setdefault('filename', None) kwargs.setdefault('content_type', 'application/octet-stream') for key, value in kwargs.iteritems(): setattr(self, key, value) self._blob = Blob() def open(self): return self._blob.open('r') def open_iterator(self): return filestream_iterator(self._blob.committed(), 'rb') def open_write(self): return self._blob.open('w') def send_data(self, RESPONSE, as_attachment=True, set_filename=True, REQUEST=None): """NyBlobFiles can also be served using X-Sendfile. In order to do so, you need to set X-NaayaEnableSendfile header to "on" by frontend server for each request. Lighttpd.conf example (working in proxy mode):: server.modules += ( "mod_setenv" ) setenv.add-request-header = ( "X-NaayaEnableSendfile" => "on" ) proxy-core.allow-x-sendfile = "enable" """ RESPONSE.setHeader('Content-Length', self.size) RESPONSE.setHeader('Content-Type', self.content_type) if as_attachment: header_value = "attachment" if set_filename: utf8_fname = urllib.quote(self.filename) header_value += ";filename*=UTF-8''%s" % utf8_fname RESPONSE.setHeader('Content-Disposition', header_value) # Test for enabling of X-SendFile if REQUEST is not None: ny_xsendfile = REQUEST.get_header("X-NaayaEnableSendfile") if ny_xsendfile is not None and ny_xsendfile=="on": RESPONSE.setHeader("X-Sendfile", self._current_filename()) return "[body should be replaced by front-end server]" if hasattr(RESPONSE, '_streaming'): return self.open_iterator() else: return self.open().read() def _current_filename(self): """ Convenience function that returns blob's filename """ try: return self._blob.committed() except BlobError: return self._blob._p_blob_uncommitted def __repr__(self): return '<%(cls)s %(fname)r (%(mime)s, %(size)r bytes)>' % { 'cls': self.__class__.__name__, 'fname': self.filename, 'mime': self.content_type, 'size': self.size, }
class BlobValue(object): """A BlobValue is using a ZODB Blob to store data. It handles both the zope.app.file and zope.file features. It can be used as a blob attribute, for more complex object. It can also be used as a mixin with a Persistent class. """ implements(IBlobFile) filename = FieldProperty(IBlobFile['filename']) mimeType = FieldProperty(IBlobFile['mimeType']) parameters = FieldProperty(IBlobFile['parameters']) def __init__(self, data='', contentType='', filename=None, parameters=None): if filename: filename = clean_filename(filename) self.filename = filename if not contentType and filename: self.mimeType, enc = guess_content_type(name=filename) elif not contentType: self.mimeType = "application/octet-stream" else: self.mimeType = contentType if parameters is None: parameters = {} else: parameters = dict(parameters) self.parameters = parameters self._blob = Blob() self.data = data @property def contentType(self): return self.mimeType def open(self, mode="r"): return self._blob.open(mode) def openDetached(self): return file(self._blob.committed(), 'rb') def __len__(self): if self._blob == "": return 0 reader = self._blob.open() reader.seek(0, 2) size = reader.tell() reader.close() return size @property def size(self): return int(self.__len__()) @apply def data(): """The blob property using a IFileStorage adapter to write down the value. """ def get(self): blob = self._blob.open('r') data = blob.read() blob.close() return data def set(self, value): stored = queryMultiAdapter((self._blob, value), IFileStorage) if stored is not True: raise StorageError( "An error occured during the blob storage. Check the " "value type (%r). This value should implement IFile, " "IString or IUnicode (see `dolmen.builtins`)." % value.__class__) return property(get, set) @property def physical_path(self): try: filename = self._blob.committed() except BlobError: # We retry, the data has now been commited # if possible by the ZODB blob. try: filename = self._blob.committed() except BlobError: # The retry failed, we return None. return None return filename
class File(Persistent): """ inspired by zope.file package implementation """ interface.implements(IFile) filename = u'file' mimeType = u'' modified = None disablePreview = False disablePrint = False def __init__(self): self._blob = Blob() self.data = u'' @setproperty def data(self, data): self.size = len(data) self.hash = md5.md5(data).hexdigest() self.modified = zope.datetime.parseDatetimetz(str(datetime.now())) fp = self.open('w') fp.write(data) fp.close() @getproperty def data(self): try: fp = self._blob.open('r') data = fp.read() fp.close() return data except POSKeyError: print "Found damaged FileField: %s" % (self.filename) return False @getproperty def previewIsAvailable(self): """ check record in previewsCatalog, returns True or False """ return getUtility(IPreviewsCatalog).check(self) @getproperty def size(self): if 'size' in self.__dict__: return self.__dict__['size'] else: reader = self.open() if not reader: return 0 try: reader.seek(0, 2) size = int(reader.tell()) finally: reader.close() self.__dict__['size'] = size return size @setproperty def size(self, value): self.__dict__['size'] = value @Lazy def hash(self): fp = self._blob.open('r') data = fp.read() fp.close() self.hash = md5.md5(data).hexdigest() self._p_changed = True return self.hash def __len__(self): return self.size def __nonzero__(self): return self.size > 0 def clear(self): self.filename = u'' self.mimeType = u'' self.data = u'' self.disablePreview = u'' self.disablePrint = u'' # NOTE: remove record from previewsCatalog getUtility(IPreviewsCatalog).remove(self) def open(self, mode="r"): try: if 'w' in mode: if 'size' in self.__dict__: del self.__dict__['size'] self.modified = zope.datetime.parseDatetimetz( str(datetime.now())) return self._blob.open(mode) except POSKeyError: print "Found damaged FileField: %s" % (self.filename) return False def openPreview(self, mode="r"): """ returns openPreview for preview """ preview = getUtility(IPreviewsCatalog).getPreview(self) return preview.openPreview(mode) def openDetached(self, n=0): try: return file(self._blob.committed(), 'rb') except BlobError: if n < 2: transaction.commit() return self.openDetached(n + 1) def openPreviewDetached(self): """ returns openPreviewDetached for preview """ preview = getUtility(IPreviewsCatalog).getPreview(self) return preview.openPreviewDetached() def _show(self, request, filename=None, contentDisposition="inline"): response = request.response if not self.mimeType: response.setHeader('Content-Type', 'application/octet-stream') else: response.setHeader('Content-Type', self.mimeType) response.setHeader('Content-Length', self.size) modified = self.modified header = request.getHeader('If-Modified-Since', None) lmt = long(zope.datetime.time(modified.isoformat())) if header is not None: header = header.split(';')[0] try: mod_since = long(zope.datetime.time(header)) except: mod_since = None if mod_since is not None: if lmt <= mod_since: response.setStatus(304) return '' response.setHeader('Last-Modified', zope.datetime.rfc1123_date(lmt)) if filename is None: filename = self.filename response.setHeader( 'Content-Disposition', '%s; filename="%s"' % ( contentDisposition, filename.encode('utf-8'))) if not self.size: response.setHeader('Content-Type', 'text/plain') return '' else: return self def show(self, *kv, **kw): res = self._show(*kv, **kw) if res != '': return DownloadResult(self) return res def showFly(self, *kv, **kw): res = self._show(*kv, **kw) if res != '': return DownloadResultFly(self) return res def _showPreview(self, request, filename=None, contentDisposition="inline"): # NOTE: previewSize for preview from previewsCatalog previewSize = getUtility(IPreviewsCatalog).getPreviewSize(self) response = request.response response.setHeader('Content-Type', 'application/x-shockwave-flash') response.setHeader('Content-Length', previewSize) modified = self.modified header = request.getHeader('If-Modified-Since', None) lmt = long(rfc822.mktime_tz(modified.utctimetuple() + (0,))) if header is not None: header = header.split(';')[0] try: mod_since = long(zope.datetime.time(header)) except: mod_since = None if mod_since is not None: if lmt <= mod_since: response.setStatus(304) return '' response.setHeader('Last-Modified', zope.datetime.rfc1123_date(lmt)) if filename is None: filename = self.filename response.setHeader( 'Content-Disposition', '%s; filename="%s"' % ( contentDisposition, filename.encode('utf-8'))) if not previewSize: response.setHeader('Content-Type', 'text/plain') return '' else: return self def showPreview(self, *kv, **kw): res = self._showPreview(*kv, **kw) if res != '': return DownloadPreviewResult(self) return res def showPreviewFly(self, *kv, **kw): res = self._showPreview(*kv, **kw) if res != '': return DownloadPreviewResultFly(self) return res def generatePreview(self): """ add record to previewsCatalog, generate preview """ getUtility(IPreviewsCatalog).add(self) def __deepcopy__(self, memo): new = File() new.data = self.data new.filename = self.filename new.mimeType = self.mimeType new.disablePreview = self.disablePreview new.disablePrint = self.disablePrint new.generatePreview() return new
class File(Persistent, Contained): """Generic file persistent object""" title = FieldProperty(IFileInfo['title']) description = FieldProperty(IFileInfo['description']) filename = FieldProperty(IFileInfo['filename']) language = FieldProperty(IFileInfo['language']) _size = 0 def __init__(self, data='', content_type=None, source=None): self.content_type = content_type self._blob = None if data: self.data = data elif source: if os.path.exists(source): try: f = open(source, 'rb') # pylint: disable=invalid-name self.data = f finally: f.close() def init_blob(self): """Initialize internal blob and add reference to it""" self.remove_blob_reference() self._blob = Blob() def add_blob_reference(self, reference=None): """Add reference to internal blob""" if self._blob is not None: references = get_utility(IBlobReferenceManager) references.add_reference( self._blob, reference if reference is not None else self) def remove_blob_reference(self): """Remove reference to internal blob Blob is deleted if there is no more reference to it. """ if self._blob is not None: references = get_utility(IBlobReferenceManager) references.drop_reference(self._blob, self) self._blob = None def get_blob(self, mode='r'): """Open current blob in given mode""" if self._blob is None: return None return self._blob.open(mode=mode) def get_detached_blob(self): """Get a detached blob, which can be used after transaction end""" if self._blob is None: return None return open(self._blob.committed(), 'rb') def _get_data(self): """Read the whole blob content""" f = self.get_blob() # pylint: disable=invalid-name if f is None: return None try: data = f.read() return data finally: f.close() def _set_data(self, data): """Set blob content""" self.init_blob() if isinstance(data, str): data = data.encode('utf-8') elif hasattr(data, 'seek'): data.seek(0) f = self.get_blob('w') # pylint: disable=invalid-name try: if hasattr(data, 'read'): self._size = 0 _data = data.read(BLOCK_SIZE) size = len(_data) while size > 0: f.write(_data) self._size += size _data = data.read(BLOCK_SIZE) size = len(_data) else: f.write(data) self._size = len(data) finally: f.close() data = property(_get_data, _set_data) def get_size(self): """Return current blob size""" return self._size def __enter__(self): """Context manager entry point""" return self.get_blob(mode='r') def __exit__(self, exc_type, exc_val, exc_tb): """Context manager exit point We don't close the blob here. It's up to the context manager call to close it! """ def __iter__(self): """Iterator over blob content to read by chunks""" if self._blob is None: return with self as f: # pylint: disable=invalid-name while True: chunk = f.read(BLOCK_SIZE) if not chunk: f.close() return yield chunk def __bool__(self): """Non-zero test against blob size""" return self._size > 0
class NyBlobFile(Item, Persistent, Cacheable, Implicit): """Naaya persistence of file using ZODB blobs""" implements(INyBlobFile) meta_type = "NyBlobFile" security = ClassSecurityInfo() def __init__(self, **kwargs): super(NyBlobFile, self).__init__(**kwargs) kwargs.setdefault('filename', None) kwargs.setdefault('content_type', 'application/octet-stream') for key, value in kwargs.iteritems(): setattr(self, key, value) self._blob = Blob() def is_broken(self): filename = self.get_filename() if not filename: return True try: os.stat(filename) return False except (OSError, POSKeyError): return True def open(self): return self._blob.open('r') def open_iterator(self): return filestream_iterator(self._blob.committed(), 'rb') def open_write(self): return self._blob.open('w') def send_data(self, RESPONSE, as_attachment=True, set_filename=True, REQUEST=None): """NyBlobFiles can also be served using X-Sendfile. In order to do so, you need to set X-NaayaEnableSendfile header to "on" by frontend server for each request. Lighttpd.conf example (working in proxy mode):: server.modules += ( "mod_setenv" ) setenv.add-request-header = ( "X-NaayaEnableSendfile" => "on" ) proxy-core.allow-x-sendfile = "enable" """ RESPONSE.setHeader('Content-Length', self.size) RESPONSE.setHeader('Content-Type', self.content_type) if as_attachment: header_value = "attachment" if set_filename: utf8_fname = urllib.quote(self.filename) header_value += ";filename*=UTF-8''%s" % utf8_fname RESPONSE.setHeader('Content-Disposition', header_value) # Test for enabling of X-SendFile if REQUEST is not None: ny_xsendfile = REQUEST.get_header("X-NaayaEnableSendfile") if ny_xsendfile is not None and ny_xsendfile=="on": RESPONSE.setHeader("X-Sendfile", self._current_filename()) return "[body should be replaced by front-end server]" if hasattr(RESPONSE, '_streaming'): return self.open_iterator() else: return self.open().read() def _current_filename(self): """ Convenience function that returns blob's filename """ try: return self._blob.committed() except POSKeyError: return None except BlobError: return self._blob._p_blob_uncommitted get_filename = _current_filename def __repr__(self): return '<%(cls)s %(fname)r (%(mime)s, %(size)r bytes)>' % { 'cls': self.__class__.__name__, 'fname': self.filename, 'mime': self.content_type, 'size': self.size, } def get_size(self): return self.size security.declareProtected("View", 'index_html') def index_html(self, REQUEST=None, RESPONSE=None, charset='utf-8', disposition='inline'): """ make it directly viewable when entering the objects URL """ if REQUEST is None: REQUEST = self.REQUEST if RESPONSE is None: RESPONSE = REQUEST.RESPONSE RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime)) RESPONSE.setHeader('Content-Type', self.getContentType()) RESPONSE.setHeader('Accept-Ranges', 'bytes') if handleIfModifiedSince(self, REQUEST, RESPONSE): return '' length = self.get_size() RESPONSE.setHeader('Content-Length', length) filename = self.getFilename() if filename is not None: if not isinstance(filename, unicode): filename = unicode(filename, charset, errors="ignore") filename = IUserPreferredFileNameNormalizer(REQUEST).normalize( filename) header_value = contentDispositionHeader( disposition=disposition, filename=filename) RESPONSE.setHeader("Content-disposition", header_value) request_range = handleRequestRange(self, length, REQUEST, RESPONSE) for fr in self._blob.readers: self._blob.readers.remove(fr) return self.getIterator(**request_range) security.declarePrivate('getIterator') def getIterator(self, **kw): """ return a filestream iterator object from the blob """ return BlobStreamIterator(self._blob, **kw) def getContentType(self): return self.content_type def getFilename(self): return self.filename def raw_data(self): f = self.open() s = f.read() f.close() for fr in self._blob.readers: self._blob.readers.remove(fr) return s
class NyBlobFile(Item, Persistent, Cacheable, Implicit): """Naaya persistence of file using ZODB blobs""" implements(INyBlobFile) meta_type = "NyBlobFile" security = ClassSecurityInfo() def __init__(self, **kwargs): super(NyBlobFile, self).__init__(**kwargs) kwargs.setdefault('filename', None) kwargs.setdefault('content_type', 'application/octet-stream') for key, value in kwargs.iteritems(): setattr(self, key, value) self._blob = Blob() def is_broken(self): filename = self.get_filename() if not filename: return True try: os.stat(filename) return False except (OSError, POSKeyError): return True def open(self): return self._blob.open('r') def open_iterator(self): return filestream_iterator(self._blob.committed(), 'rb') def open_write(self): return self._blob.open('w') def send_data(self, RESPONSE, as_attachment=True, set_filename=True, REQUEST=None): """NyBlobFiles can also be served using X-Sendfile. In order to do so, you need to set X-NaayaEnableSendfile header to "on" by frontend server for each request. Lighttpd.conf example (working in proxy mode):: server.modules += ( "mod_setenv" ) setenv.add-request-header = ( "X-NaayaEnableSendfile" => "on" ) proxy-core.allow-x-sendfile = "enable" """ RESPONSE.setHeader('Content-Length', self.size) RESPONSE.setHeader('Content-Type', self.content_type) if as_attachment: header_value = "attachment" if set_filename: utf8_fname = urllib.quote(self.filename) header_value += ";filename*=UTF-8''%s" % utf8_fname RESPONSE.setHeader('Content-Disposition', header_value) # Test for enabling of X-SendFile if REQUEST is not None: ny_xsendfile = REQUEST.get_header("X-NaayaEnableSendfile") if ny_xsendfile is not None and ny_xsendfile == "on": RESPONSE.setHeader("X-Sendfile", self._current_filename()) return "[body should be replaced by front-end server]" if hasattr(RESPONSE, '_streaming'): return self.open_iterator() else: return self.open().read() def _current_filename(self): """ Convenience function that returns blob's filename """ try: return self._blob.committed() except POSKeyError: return None except BlobError: return self._blob._p_blob_uncommitted get_filename = _current_filename def __repr__(self): return '<%(cls)s %(fname)r (%(mime)s, %(size)r bytes)>' % { 'cls': self.__class__.__name__, 'fname': self.filename, 'mime': self.content_type, 'size': self.size, } def get_size(self): return self.size security.declareProtected("View", 'index_html') def index_html(self, REQUEST=None, RESPONSE=None, charset='utf-8', disposition='inline'): """ make it directly viewable when entering the objects URL """ if REQUEST is None: REQUEST = self.REQUEST if RESPONSE is None: RESPONSE = REQUEST.RESPONSE RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime)) RESPONSE.setHeader('Content-Type', self.getContentType()) RESPONSE.setHeader('Accept-Ranges', 'bytes') if handleIfModifiedSince(self, REQUEST, RESPONSE): return '' length = self.get_size() RESPONSE.setHeader('Content-Length', length) filename = self.getFilename() if filename is not None: if not isinstance(filename, unicode): filename = unicode(filename, charset, errors="ignore") filename = IUserPreferredFileNameNormalizer(REQUEST).normalize( filename) header_value = contentDispositionHeader(disposition=disposition, filename=filename) RESPONSE.setHeader("Content-disposition", header_value) request_range = handleRequestRange(self, length, REQUEST, RESPONSE) for fr in self._blob.readers: self._blob.readers.remove(fr) return self.getIterator(**request_range) security.declarePrivate('getIterator') def getIterator(self, **kw): """ return a filestream iterator object from the blob """ return BlobStreamIterator(self._blob, **kw) def getContentType(self): return self.content_type def getFilename(self): return self.filename def raw_data(self): f = self.open() s = f.read() f.close() for fr in self._blob.readers: self._blob.readers.remove(fr) return s
class PreviewRecord(Persistent): interface.implements(IPreviewRecord) def __init__(self, parent=None): self.parent = parent self.previewSize = 0 self._previewBlob = Blob() @getproperty def previewData(self): """ returns preview data """ fp = self._previewBlob.open('r') data = fp.read() fp.close() return data @getproperty def previewSize(self): if 'previewSize' in self.__dict__: return self.__dict__['previewSize'] else: reader = self.openPreview() try: reader.seek(0,2) size = int(reader.tell()) finally: reader.close() self.__dict__['previewSize'] = size return size @setproperty def previewSize(self, value): self.__dict__['previewSize'] = value def openPreview(self, mode="r"): try: return self._previewBlob.open(mode) except AttributeError: self._previewBlob = Blob() return self._previewBlob.open(mode) def openPreviewDetached(self, n=0): try: return file(self._previewBlob.committed(), 'rb') except BlobError: if n < 2: transaction.commit() return self.openPreviewDetached(n + 1) def generatePreview(self): MAX_VALUE = getUtility(IPreviewsCatalog).maxValue * 1024 * 1024 size = 0 if self.parent.size < MAX_VALUE and not self.parent.disablePreview: logger.info( "Start generating preview for: %s" % self.parent.filename) fp = self.openPreview('w') ff = self.parent.open() try: fp.write(api.convert( ff, 'application/x-shockwave-flash', self.parent.mimeType, filename=self.parent.filename)) size = int(fp.tell()) except (ConverterException, OSError), e: logger.warning( 'Error generating preview for %s: %s', self.parent.filename, e) except: