X-Git-Url: https://scm.cri.minesparis.psl.eu/git/Photo.git/blobdiff_plain/a18ca54d896fa2f98cf2b7fb8955120ff5e0aa37..refs/heads/zope-2.12:/blobbases.py?ds=sidebyside

diff --git a/blobbases.py b/blobbases.py
index 9d2fb6f..964ffe3 100755
--- a/blobbases.py
+++ b/blobbases.py
@@ -14,799 +14,806 @@
 ##############################################################################
 """Image object
 
-$Id: blobbases.py 949 2009-04-30 14:42:24Z pin $
-$URL: http://svn.luxia.fr/svn/labo/projects/zope/Photo/trunk/blobbases.py $
 """
 
+from cgi import escape
+from cStringIO import StringIO
+from mimetools import choose_boundary
 import struct
 from warnings import warn
-from zope.contenttype import guess_content_type
-from Globals import DTMLFile
-from Globals import InitializeClass
-from OFS.PropertyManager import PropertyManager
-from AccessControl import ClassSecurityInfo
-from AccessControl.Role import RoleManager
+
 from AccessControl.Permissions import change_images_and_files
 from AccessControl.Permissions import view_management_screens
 from AccessControl.Permissions import view as View
 from AccessControl.Permissions import ftp_access
 from AccessControl.Permissions import delete_objects
+from AccessControl.Role import RoleManager
+from AccessControl.SecurityInfo import ClassSecurityInfo
+from Acquisition import Implicit
+from App.class_init import InitializeClass
+from App.special_dtml import DTMLFile
+from DateTime.DateTime import DateTime
+from Persistence import Persistent
 from webdav.common import rfc1123_date
+from webdav.interfaces import IWriteLock
 from webdav.Lockable import ResourceLockedError
-from webdav.WriteLockInterface import WriteLockInterface
-from OFS.SimpleItem import Item_w__name__
-from cStringIO import StringIO
-from Globals import Persistent
-from Acquisition import Implicit
-from DateTime import DateTime
-from OFS.Cache import Cacheable
-from mimetools import choose_boundary
 from ZPublisher import HTTPRangeSupport
 from ZPublisher.HTTPRequest import FileUpload
 from ZPublisher.Iterators import filestream_iterator
 from zExceptions import Redirect
-from cgi import escape
-import transaction
+from zope.contenttype import guess_content_type
+from zope.interface import implementedBy
+from zope.interface import implements
+
+from OFS.Cache import Cacheable
+from OFS.PropertyManager import PropertyManager
+from OFS.SimpleItem import Item_w__name__
+
+from zope.event import notify
+from zope.lifecycleevent import ObjectModifiedEvent
+from zope.lifecycleevent import ObjectCreatedEvent
+
 from ZODB.blob import Blob
 
 CHUNK_SIZE = 1 << 16
 
-manage_addFileForm=DTMLFile('dtml/imageAdd', globals(),Kind='File',kind='file')
-def manage_addFile(self,id,file='',title='',precondition='', content_type='',
-				   REQUEST=None):
-	"""Add a new File object.
 
-	Creates a new File object 'id' with the contents of 'file'"""
+manage_addFileForm = DTMLFile('dtml/imageAdd',
+                              globals(),
+                              Kind='File',
+                              kind='file',
+                             )
+def manage_addFile(self, id, file='', title='', precondition='',
+                   content_type='', REQUEST=None):
+    """Add a new File object.
 
-	id=str(id)
-	title=str(title)
-	content_type=str(content_type)
-	precondition=str(precondition)
+    Creates a new File object 'id' with the contents of 'file'"""
 
-	id, title = cookId(id, title, file)
+    id = str(id)
+    title = str(title)
+    content_type = str(content_type)
+    precondition = str(precondition)
 
-	self=self.this()
-	self._setObject(id, File(id,title,file,content_type, precondition))
+    id, title = cookId(id, title, file)
+    
+    self=self.this()
+    self._setObject(id, File(id,title,file,content_type, precondition))
 
-	if REQUEST is not None:
-		REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
+    newFile = self._getOb(id)
+    notify(ObjectCreatedEvent(newFile))
+    
+    if REQUEST is not None:
+        REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
 
 
 class File(Persistent, Implicit, PropertyManager,
-		   RoleManager, Item_w__name__, Cacheable):
-	"""A File object is a content object for arbitrary files."""
-
-	__implements__ = (WriteLockInterface, HTTPRangeSupport.HTTPRangeInterface)
-	meta_type='Blob File'
-
-	security = ClassSecurityInfo()
-	security.declareObjectProtected(View)
-
-	precondition=''
-	size=None
-
-	manage_editForm	 =DTMLFile('dtml/fileEdit',globals(),
-							   Kind='File',kind='file')
-	manage_editForm._setName('manage_editForm')
-
-	security.declareProtected(view_management_screens, 'manage')
-	security.declareProtected(view_management_screens, 'manage_main')
-	manage=manage_main=manage_editForm
-	manage_uploadForm=manage_editForm
-
-	manage_options=(
-		(
-		{'label':'Edit', 'action':'manage_main',
-		 'help':('OFSP','File_Edit.stx')},
-		{'label':'View', 'action':'',
-		 'help':('OFSP','File_View.stx')},
-		)
-		+ PropertyManager.manage_options
-		+ RoleManager.manage_options
-		+ Item_w__name__.manage_options
-		+ Cacheable.manage_options
-		)
-
-	_properties=({'id':'title', 'type': 'string'},
-				 {'id':'content_type', 'type':'string'},
-				 )
-
-	def __init__(self, id, title, file, content_type='', precondition=''):
-		self.__name__=id
-		self.title=title
-		self.precondition=precondition
-		self.uploaded_filename = cookId('', '', file)[0]
-		self.bdata = Blob()
-
-		content_type=self._get_content_type(file, id, content_type)
-		self.update_data(file, content_type)
-	
-	security.declarePrivate('save')
-	def save(self, file):
-		bf = self.bdata.open('w')
-		bf.write(file.read())
-		self.size = bf.tell()
-		bf.close()
-	
-	security.declarePrivate('open')
-	def open(self, mode='r'):
-		bf = self.bdata.open(mode)
-		return bf
-	
-	security.declarePrivate('updateSize')
-	def updateSize(self, size=None):
-		if size is None :
-			bf = self.open('r')
-			bf.seek(0,2)
-			self.size = bf.tell()
-			bf.close()
-		else :
-			self.size = size
-
-	def _getLegacyData(self) :
-		warn("Accessing 'data' attribute may be inefficient with "
-			 "this blob based file. You should refactor your product "
-			 "by accessing data like: "
-			 "f = self.open('r') "
-			 "data = f.read()",
-			DeprecationWarning, stacklevel=2)
-		f = self.open()
-		data = f.read()
-		f.close()
-		return data
-	
-	def _setLegacyData(self, data) :
-		warn("Accessing 'data' attribute may be inefficient with "
-			 "this blob based file. You should refactor your product "
-			 "by accessing data like: "
-			 "f = self.save(data)",
-			DeprecationWarning, stacklevel=2)
-		if isinstance(data, str) :
-			sio = StringIO()
-			sio.write(data)
-			sio.seek(0)
-			data = sio
-		self.save(data)
-		
-	data = property(_getLegacyData, _setLegacyData,
-					"Data Legacy attribute to ensure compatibility "
-					"with derived classes that access data by this way.")
-
-	def id(self):
-		return self.__name__
-
-	def _if_modified_since_request_handler(self, REQUEST, RESPONSE):
-		# HTTP If-Modified-Since header handling: return True if
-		# we can handle this request by returning a 304 response
-		header=REQUEST.get_header('If-Modified-Since', None)
-		if header is not None:
-			header=header.split( ';')[0]
-			# Some proxies seem to send invalid date strings for this
-			# header. If the date string is not valid, we ignore it
-			# rather than raise an error to be generally consistent
-			# with common servers such as Apache (which can usually
-			# understand the screwy date string as a lucky side effect
-			# of the way they parse it).
-			# This happens to be what RFC2616 tells us to do in the face of an
-			# invalid date.
-			try:	mod_since=long(DateTime(header).timeTime())
-			except: mod_since=None
-			if mod_since is not None:
-				if self._p_mtime:
-					last_mod = long(self._p_mtime)
-				else:
-					last_mod = long(0)
-				if last_mod > 0 and last_mod <= mod_since:
-					RESPONSE.setHeader('Last-Modified',
-									   rfc1123_date(self._p_mtime))
-					RESPONSE.setHeader('Content-Type', self.content_type)
-					RESPONSE.setHeader('Accept-Ranges', 'bytes')
-					RESPONSE.setStatus(304)
-					return True
-
-	def _range_request_handler(self, REQUEST, RESPONSE):
-		# HTTP Range header handling: return True if we've served a range
-		# chunk out of our data.
-		range = REQUEST.get_header('Range', None)
-		request_range = REQUEST.get_header('Request-Range', None)
-		if request_range is not None:
-			# Netscape 2 through 4 and MSIE 3 implement a draft version
-			# Later on, we need to serve a different mime-type as well.
-			range = request_range
-		if_range = REQUEST.get_header('If-Range', None)
-		if range is not None:
-			ranges = HTTPRangeSupport.parseRange(range)
-
-			if if_range is not None:
-				# Only send ranges if the data isn't modified, otherwise send
-				# the whole object. Support both ETags and Last-Modified dates!
-				if len(if_range) > 1 and if_range[:2] == 'ts':
-					# ETag:
-					if if_range != self.http__etag():
-						# Modified, so send a normal response. We delete
-						# the ranges, which causes us to skip to the 200
-						# response.
-						ranges = None
-				else:
-					# Date
-					date = if_range.split( ';')[0]
-					try: mod_since=long(DateTime(date).timeTime())
-					except: mod_since=None
-					if mod_since is not None:
-						if self._p_mtime:
-							last_mod = long(self._p_mtime)
-						else:
-							last_mod = long(0)
-						if last_mod > mod_since:
-							# Modified, so send a normal response. We delete
-							# the ranges, which causes us to skip to the 200
-							# response.
-							ranges = None
-
-			if ranges:
-				# Search for satisfiable ranges.
-				satisfiable = 0
-				for start, end in ranges:
-					if start < self.size:
-						satisfiable = 1
-						break
-
-				if not satisfiable:
-					RESPONSE.setHeader('Content-Range',
-						'bytes */%d' % self.size)
-					RESPONSE.setHeader('Accept-Ranges', 'bytes')
-					RESPONSE.setHeader('Last-Modified',
-						rfc1123_date(self._p_mtime))
-					RESPONSE.setHeader('Content-Type', self.content_type)
-					RESPONSE.setHeader('Content-Length', self.size)
-					RESPONSE.setStatus(416)
-					return True
-
-				ranges = HTTPRangeSupport.expandRanges(ranges, self.size)
-
-				if len(ranges) == 1:
-					# Easy case, set extra header and return partial set.
-					start, end = ranges[0]
-					size = end - start
-
-					RESPONSE.setHeader('Last-Modified',
-						rfc1123_date(self._p_mtime))
-					RESPONSE.setHeader('Content-Type', self.content_type)
-					RESPONSE.setHeader('Content-Length', size)
-					RESPONSE.setHeader('Accept-Ranges', 'bytes')
-					RESPONSE.setHeader('Content-Range',
-						'bytes %d-%d/%d' % (start, end - 1, self.size))
-					RESPONSE.setStatus(206) # Partial content
-
-					bf = self.open('r')
-					bf.seek(start)
-					RESPONSE.write(bf.read(size))
-					bf.close()
-					return True
-
-				else:
-					boundary = choose_boundary()
-
-					# Calculate the content length
-					size = (8 + len(boundary) + # End marker length
-						len(ranges) * (			# Constant lenght per set
-							49 + len(boundary) + len(self.content_type) +
-							len('%d' % self.size)))
-					for start, end in ranges:
-						# Variable length per set
-						size = (size + len('%d%d' % (start, end - 1)) +
-							end - start)
-
-
-					# Some clients implement an earlier draft of the spec, they
-					# will only accept x-byteranges.
-					draftprefix = (request_range is not None) and 'x-' or ''
-
-					RESPONSE.setHeader('Content-Length', size)
-					RESPONSE.setHeader('Accept-Ranges', 'bytes')
-					RESPONSE.setHeader('Last-Modified',
-						rfc1123_date(self._p_mtime))
-					RESPONSE.setHeader('Content-Type',
-						'multipart/%sbyteranges; boundary=%s' % (
-							draftprefix, boundary))
-					RESPONSE.setStatus(206) # Partial content
-
-					bf = self.open('r')
-
-					for start, end in ranges:
-						RESPONSE.write('\r\n--%s\r\n' % boundary)
-						RESPONSE.write('Content-Type: %s\r\n' %
-							self.content_type)
-						RESPONSE.write(
-							'Content-Range: bytes %d-%d/%d\r\n\r\n' % (
-								start, end - 1, self.size))
-
-						
-						size = end - start
-						bf.seek(start)
-						RESPONSE.write(bf.read(size))
-					
-					bf.close()
-
-					RESPONSE.write('\r\n--%s--\r\n' % boundary)
-					return True
-
-	security.declareProtected(View, 'index_html')
-	def index_html(self, REQUEST, RESPONSE):
-		"""
-		The default view of the contents of a File or Image.
-
-		Returns the contents of the file or image.	Also, sets the
-		Content-Type HTTP header to the objects content type.
-		"""
-
-		if self._if_modified_since_request_handler(REQUEST, RESPONSE):
-			# we were able to handle this by returning a 304
-			# unfortunately, because the HTTP cache manager uses the cache
-			# API, and because 304 responses are required to carry the Expires
-			# header for HTTP/1.1, we need to call ZCacheable_set here.
-			# This is nonsensical for caches other than the HTTP cache manager
-			# unfortunately.
-			self.ZCacheable_set(None)
-			return ''
-
-		if self.precondition and hasattr(self, str(self.precondition)):
-			# Grab whatever precondition was defined and then
-			# execute it.  The precondition will raise an exception
-			# if something violates its terms.
-			c=getattr(self, str(self.precondition))
-			if hasattr(c,'isDocTemp') and c.isDocTemp:
-				c(REQUEST['PARENTS'][1],REQUEST)
-			else:
-				c()
-
-		if self._range_request_handler(REQUEST, RESPONSE):
-			# we served a chunk of content in response to a range request.
-			return ''
-
-		RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime))
-		RESPONSE.setHeader('Content-Type', self.content_type)
-		RESPONSE.setHeader('Content-Length', self.size)
-		RESPONSE.setHeader('Accept-Ranges', 'bytes')
-
-		if self.ZCacheable_isCachingEnabled():
-			result = self.ZCacheable_get(default=None)
-			if result is not None:
-				# We will always get None from RAMCacheManager and HTTP
-				# Accelerated Cache Manager but we will get
-				# something implementing the IStreamIterator interface
-				# from a "FileCacheManager"
-				return result
-
-		self.ZCacheable_set(None)
-
-		bf = self.open('r')
-		chunk = bf.read(CHUNK_SIZE)
-		while chunk :
-			RESPONSE.write(chunk)
-			chunk = bf.read(CHUNK_SIZE)
-		bf.close()
-		return ''		
-
-	security.declareProtected(View, 'view_image_or_file')
-	def view_image_or_file(self, URL1):
-		"""
-		The default view of the contents of the File or Image.
-		"""
-		raise Redirect, URL1
-
-	security.declareProtected(View, 'PrincipiaSearchSource')
-	def PrincipiaSearchSource(self):
-		""" Allow file objects to be searched.
-		"""
-		if self.content_type.startswith('text/'):
-			bf = self.open('r')
-			data = bf.read()
-			bf.close()
-			return data
-		return ''
-
-	security.declarePrivate('update_data')
-	def update_data(self, file, content_type=None):
-		if isinstance(file, unicode):
-			raise TypeError('Data can only be str or file-like.	 '
-							'Unicode objects are expressly forbidden.')
-		elif isinstance(file, str) :
-			sio = StringIO()
-			sio.write(file)
-			sio.seek(0)
-			file = sio
-
-		if content_type is not None: self.content_type=content_type
-		self.save(file)
-		self.ZCacheable_invalidate()
-		self.ZCacheable_set(None)
-		self.http__refreshEtag()
-
-	security.declareProtected(change_images_and_files, 'manage_edit')
-	def manage_edit(self, title, content_type, precondition='',
-					filedata=None, REQUEST=None):
-		"""
-		Changes the title and content type attributes of the File or Image.
-		"""
-		if self.wl_isLocked():
-			raise ResourceLockedError, "File is locked via WebDAV"
-
-		self.title=str(title)
-		self.content_type=str(content_type)
-		if precondition: self.precondition=str(precondition)
-		elif self.precondition: del self.precondition
-		if filedata is not None:
-			self.update_data(filedata, content_type)
-		else:
-			self.ZCacheable_invalidate()
-		if REQUEST:
-			message="Saved changes."
-			return self.manage_main(self,REQUEST,manage_tabs_message=message)
-
-	security.declareProtected(change_images_and_files, 'manage_upload')
-	def manage_upload(self,file='',REQUEST=None):
-		"""
-		Replaces the current contents of the File or Image object with file.
-
-		The file or images contents are replaced with the contents of 'file'.
-		"""
-		if self.wl_isLocked():
-			raise ResourceLockedError, "File is locked via WebDAV"
-
-		content_type=self._get_content_type(file, self.__name__,
-											'application/octet-stream')
-		self.update_data(file, content_type)
-
-		if REQUEST:
-			message="Saved changes."
-			return self.manage_main(self,REQUEST,manage_tabs_message=message)
-
-	def _get_content_type(self, file, id, content_type=None):
-		headers=getattr(file, 'headers', None)
-		if headers and headers.has_key('content-type'):
-			content_type=headers['content-type']
-		else:
-			name = getattr(file, 'filename', self.uploaded_filename) or id
-			content_type, enc=guess_content_type(name, '', content_type)
-		return content_type
-
-	security.declareProtected(delete_objects, 'DELETE')
-
-	security.declareProtected(change_images_and_files, 'PUT')
-	def PUT(self, REQUEST, RESPONSE):
-		"""Handle HTTP PUT requests"""
-		self.dav__init(REQUEST, RESPONSE)
-		self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
-		type=REQUEST.get_header('content-type', None)
-
-		file=REQUEST['BODYFILE']
-
-		content_type = self._get_content_type(file, self.__name__,
-											  type or self.content_type)
-		self.update_data(file, content_type)
-
-		RESPONSE.setStatus(204)
-		return RESPONSE
-
-	security.declareProtected(View, 'get_size')
-	def get_size(self):
-		"""Get the size of a file or image.
-
-		Returns the size of the file or image.
-		"""
-		size=self.size
-		if size is None :
-			bf = self.open('r')
-			bf.seek(0,2)
-			self.size = size = bf.tell()
-			bf.close()
-		return size
-
-	# deprecated; use get_size!
-	getSize=get_size
-
-	security.declareProtected(View, 'getContentType')
-	def getContentType(self):
-		"""Get the content type of a file or image.
-
-		Returns the content type (MIME type) of a file or image.
-		"""
-		return self.content_type
-
-
-	def __str__(self): return str(self.data)
-	def __len__(self): return 1
-
-	security.declareProtected(ftp_access, 'manage_FTPstat')
-	security.declareProtected(ftp_access, 'manage_FTPlist')
-
-	security.declareProtected(ftp_access, 'manage_FTPget')
-	def manage_FTPget(self):
-		"""Return body for ftp."""
-		RESPONSE = self.REQUEST.RESPONSE
-
-		if self.ZCacheable_isCachingEnabled():
-			result = self.ZCacheable_get(default=None)
-			if result is not None:
-				# We will always get None from RAMCacheManager but we will get
-				# something implementing the IStreamIterator interface
-				# from FileCacheManager.
-				# the content-length is required here by HTTPResponse, even
-				# though FTP doesn't use it.
-				RESPONSE.setHeader('Content-Length', self.size)
-				return result
-
-		bf = self.open('r')
-		data = bf.read()
-		bf.close()
-		RESPONSE.setBase(None)
-		return data
+           RoleManager, Item_w__name__, Cacheable):
+    """A File object is a content object for arbitrary files."""
+
+    implements(implementedBy(Persistent),
+               implementedBy(Implicit),
+               implementedBy(PropertyManager),
+               implementedBy(RoleManager),
+               implementedBy(Item_w__name__),
+               implementedBy(Cacheable),
+               IWriteLock,
+               HTTPRangeSupport.HTTPRangeInterface,
+              )
+    meta_type='Blob File'
+
+    security = ClassSecurityInfo()
+    security.declareObjectProtected(View)
+
+    precondition=''
+    size=None
+
+    manage_editForm  =DTMLFile('dtml/fileEdit',globals(),
+                               Kind='File',kind='file')
+    manage_editForm._setName('manage_editForm')
+
+    security.declareProtected(view_management_screens, 'manage')
+    security.declareProtected(view_management_screens, 'manage_main')
+    manage=manage_main=manage_editForm
+    manage_uploadForm=manage_editForm
+
+    manage_options=(
+        (
+        {'label':'Edit', 'action':'manage_main',
+         'help':('OFSP','File_Edit.stx')},
+        {'label':'View', 'action':'',
+         'help':('OFSP','File_View.stx')},
+        )
+        + PropertyManager.manage_options
+        + RoleManager.manage_options
+        + Item_w__name__.manage_options
+        + Cacheable.manage_options
+        )
+
+    _properties=({'id':'title', 'type': 'string'},
+                 {'id':'content_type', 'type':'string'},
+                 )
+
+    def __init__(self, id, title, file, content_type='', precondition=''):
+        self.__name__=id
+        self.title=title
+        self.precondition=precondition
+        self.uploaded_filename = cookId('', '', file)[0]
+        self.bdata = Blob()
+
+        content_type=self._get_content_type(file, id, content_type)
+        self.update_data(file, content_type)
+
+    security.declarePrivate('save')
+    def save(self, file):
+        bf = self.bdata.open('w')
+        bf.write(file.read())
+        self.size = bf.tell()
+        bf.close()
+    
+    security.declarePrivate('open')
+    def open(self, mode='r'):
+        bf = self.bdata.open(mode)
+        return bf
+    
+    security.declarePrivate('updateSize')
+    def updateSize(self, size=None):
+        if size is None :
+            bf = self.open('r')
+            bf.seek(0,2)
+            self.size = bf.tell()
+            bf.close()
+        else :
+            self.size = size
+
+    def _getLegacyData(self) :
+        warn("Accessing 'data' attribute may be inefficient with "
+             "this blob based file. You should refactor your product "
+             "by accessing data like: "
+             "f = self.open('r') "
+             "data = f.read()",
+            DeprecationWarning, stacklevel=2)
+        f = self.open()
+        data = f.read()
+        f.close()
+        return data
+    
+    def _setLegacyData(self, data) :
+        warn("Accessing 'data' attribute may be inefficient with "
+             "this blob based file. You should refactor your product "
+             "by accessing data like: "
+             "f = self.save(data)",
+            DeprecationWarning, stacklevel=2)
+        if isinstance(data, str) :
+            sio = StringIO()
+            sio.write(data)
+            sio.seek(0)
+            data = sio
+        self.save(data)
+        
+    data = property(_getLegacyData, _setLegacyData,
+                    "Data Legacy attribute to ensure compatibility "
+                    "with derived classes that access data by this way.")
+
+    def id(self):
+        return self.__name__
+
+    def _if_modified_since_request_handler(self, REQUEST, RESPONSE):
+        # HTTP If-Modified-Since header handling: return True if
+        # we can handle this request by returning a 304 response
+        header=REQUEST.get_header('If-Modified-Since', None)
+        if header is not None:
+            header=header.split( ';')[0]
+            # Some proxies seem to send invalid date strings for this
+            # header. If the date string is not valid, we ignore it
+            # rather than raise an error to be generally consistent
+            # with common servers such as Apache (which can usually
+            # understand the screwy date string as a lucky side effect
+            # of the way they parse it).
+            # This happens to be what RFC2616 tells us to do in the face of an
+            # invalid date.
+            try:    mod_since=long(DateTime(header).timeTime())
+            except: mod_since=None
+            if mod_since is not None:
+                if self._p_mtime:
+                    last_mod = long(self._p_mtime)
+                else:
+                    last_mod = long(0)
+                if last_mod > 0 and last_mod <= mod_since:
+                    RESPONSE.setHeader('Last-Modified',
+                                       rfc1123_date(self._p_mtime))
+                    RESPONSE.setHeader('Content-Type', self.content_type)
+                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                    RESPONSE.setStatus(304)
+                    return True
+
+    def _range_request_handler(self, REQUEST, RESPONSE):
+        # HTTP Range header handling: return True if we've served a range
+        # chunk out of our data.
+        range = REQUEST.get_header('Range', None)
+        request_range = REQUEST.get_header('Request-Range', None)
+        if request_range is not None:
+            # Netscape 2 through 4 and MSIE 3 implement a draft version
+            # Later on, we need to serve a different mime-type as well.
+            range = request_range
+        if_range = REQUEST.get_header('If-Range', None)
+        if range is not None:
+            ranges = HTTPRangeSupport.parseRange(range)
+
+            if if_range is not None:
+                # Only send ranges if the data isn't modified, otherwise send
+                # the whole object. Support both ETags and Last-Modified dates!
+                if len(if_range) > 1 and if_range[:2] == 'ts':
+                    # ETag:
+                    if if_range != self.http__etag():
+                        # Modified, so send a normal response. We delete
+                        # the ranges, which causes us to skip to the 200
+                        # response.
+                        ranges = None
+                else:
+                    # Date
+                    date = if_range.split( ';')[0]
+                    try: mod_since=long(DateTime(date).timeTime())
+                    except: mod_since=None
+                    if mod_since is not None:
+                        if self._p_mtime:
+                            last_mod = long(self._p_mtime)
+                        else:
+                            last_mod = long(0)
+                        if last_mod > mod_since:
+                            # Modified, so send a normal response. We delete
+                            # the ranges, which causes us to skip to the 200
+                            # response.
+                            ranges = None
+
+            if ranges:
+                # Search for satisfiable ranges.
+                satisfiable = 0
+                for start, end in ranges:
+                    if start < self.size:
+                        satisfiable = 1
+                        break
+
+                if not satisfiable:
+                    RESPONSE.setHeader('Content-Range',
+                        'bytes */%d' % self.size)
+                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                    RESPONSE.setHeader('Last-Modified',
+                        rfc1123_date(self._p_mtime))
+                    RESPONSE.setHeader('Content-Type', self.content_type)
+                    RESPONSE.setHeader('Content-Length', self.size)
+                    RESPONSE.setStatus(416)
+                    return True
+
+                ranges = HTTPRangeSupport.expandRanges(ranges, self.size)
+
+                if len(ranges) == 1:
+                    # Easy case, set extra header and return partial set.
+                    start, end = ranges[0]
+                    size = end - start
+
+                    RESPONSE.setHeader('Last-Modified',
+                        rfc1123_date(self._p_mtime))
+                    RESPONSE.setHeader('Content-Type', self.content_type)
+                    RESPONSE.setHeader('Content-Length', size)
+                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                    RESPONSE.setHeader('Content-Range',
+                        'bytes %d-%d/%d' % (start, end - 1, self.size))
+                    RESPONSE.setStatus(206) # Partial content
+                    
+                    bf = self.open('r')
+                    bf.seek(start)
+                    RESPONSE.write(bf.read(size))
+                    bf.close()
+                    return True
+
+                else:
+                    boundary = choose_boundary()
+
+                    # Calculate the content length
+                    size = (8 + len(boundary) + # End marker length
+                        len(ranges) * (         # Constant lenght per set
+                            49 + len(boundary) + len(self.content_type) +
+                            len('%d' % self.size)))
+                    for start, end in ranges:
+                        # Variable length per set
+                        size = (size + len('%d%d' % (start, end - 1)) +
+                            end - start)
+
+
+                    # Some clients implement an earlier draft of the spec, they
+                    # will only accept x-byteranges.
+                    draftprefix = (request_range is not None) and 'x-' or ''
+
+                    RESPONSE.setHeader('Content-Length', size)
+                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                    RESPONSE.setHeader('Last-Modified',
+                        rfc1123_date(self._p_mtime))
+                    RESPONSE.setHeader('Content-Type',
+                        'multipart/%sbyteranges; boundary=%s' % (
+                            draftprefix, boundary))
+                    RESPONSE.setStatus(206) # Partial content
+
+
+                    bf = self.open('r')
+#                    data = self.data
+#                    # The Pdata map allows us to jump into the Pdata chain
+#                    # arbitrarily during out-of-order range searching.
+#                    pdata_map = {}
+#                    pdata_map[0] = data
+
+                    for start, end in ranges:
+                        RESPONSE.write('\r\n--%s\r\n' % boundary)
+                        RESPONSE.write('Content-Type: %s\r\n' %
+                            self.content_type)
+                        RESPONSE.write(
+                            'Content-Range: bytes %d-%d/%d\r\n\r\n' % (
+                                start, end - 1, self.size))
+
+                        
+                        size = end - start
+                        bf.seek(start)
+                        RESPONSE.write(bf.read(size))
+                    
+                    bf.close()
+
+                    RESPONSE.write('\r\n--%s--\r\n' % boundary)
+                    return True
+
+    security.declareProtected(View, 'index_html')
+    def index_html(self, REQUEST, RESPONSE):
+        """
+        The default view of the contents of a File or Image.
+
+        Returns the contents of the file or image.  Also, sets the
+        Content-Type HTTP header to the objects content type.
+        """
+
+        if self._if_modified_since_request_handler(REQUEST, RESPONSE):
+            # we were able to handle this by returning a 304
+            # unfortunately, because the HTTP cache manager uses the cache
+            # API, and because 304 responses are required to carry the Expires
+            # header for HTTP/1.1, we need to call ZCacheable_set here.
+            # This is nonsensical for caches other than the HTTP cache manager
+            # unfortunately.
+            self.ZCacheable_set(None)
+            return ''
+
+        if self.precondition and hasattr(self, str(self.precondition)):
+            # Grab whatever precondition was defined and then
+            # execute it.  The precondition will raise an exception
+            # if something violates its terms.
+            c=getattr(self, str(self.precondition))
+            if hasattr(c,'isDocTemp') and c.isDocTemp:
+                c(REQUEST['PARENTS'][1],REQUEST)
+            else:
+                c()
+
+        if self._range_request_handler(REQUEST, RESPONSE):
+            # we served a chunk of content in response to a range request.
+            return ''
+
+        RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime))
+        RESPONSE.setHeader('Content-Type', self.content_type)
+        RESPONSE.setHeader('Content-Length', self.size)
+        RESPONSE.setHeader('Accept-Ranges', 'bytes')
+
+        if self.ZCacheable_isCachingEnabled():
+            result = self.ZCacheable_get(default=None)
+            if result is not None:
+                # We will always get None from RAMCacheManager and HTTP
+                # Accelerated Cache Manager but we will get
+                # something implementing the IStreamIterator interface
+                # from a "FileCacheManager"
+                return result
+
+        self.ZCacheable_set(None)
+
+        bf = self.open('r')
+        chunk = bf.read(CHUNK_SIZE)
+        while chunk :
+            RESPONSE.write(chunk)
+            chunk = bf.read(CHUNK_SIZE)
+        bf.close()
+        return ''       
+
+    security.declareProtected(View, 'view_image_or_file')
+    def view_image_or_file(self, URL1):
+        """
+        The default view of the contents of the File or Image.
+        """
+        raise Redirect, URL1
+
+    security.declareProtected(View, 'PrincipiaSearchSource')
+    def PrincipiaSearchSource(self):
+        """ Allow file objects to be searched.
+        """
+        if self.content_type.startswith('text/'):
+            bf = self.open('r')
+            data = bf.read()
+            bf.close()
+            return data
+        return ''
+
+    security.declarePrivate('update_data')
+    def update_data(self, file, content_type=None):
+        if isinstance(file, unicode):
+            raise TypeError('Data can only be str or file-like.  '
+                            'Unicode objects are expressly forbidden.')
+        elif isinstance(file, str) :
+            sio = StringIO()
+            sio.write(file)
+            sio.seek(0)
+            file = sio
+
+        if content_type is not None: self.content_type=content_type
+        self.save(file)
+        self.ZCacheable_invalidate()
+        self.ZCacheable_set(None)
+        self.http__refreshEtag()
+
+    security.declareProtected(change_images_and_files, 'manage_edit')
+    def manage_edit(self, title, content_type, precondition='',
+                    filedata=None, REQUEST=None):
+        """
+        Changes the title and content type attributes of the File or Image.
+        """
+        if self.wl_isLocked():
+            raise ResourceLockedError, "File is locked via WebDAV"
+
+        self.title=str(title)
+        self.content_type=str(content_type)
+        if precondition: self.precondition=str(precondition)
+        elif self.precondition: del self.precondition
+        if filedata is not None:
+            self.update_data(filedata, content_type)
+        else:
+            self.ZCacheable_invalidate()
+        
+        notify(ObjectModifiedEvent(self))
+        
+        if REQUEST:
+            message="Saved changes."
+            return self.manage_main(self,REQUEST,manage_tabs_message=message)
+
+    security.declareProtected(change_images_and_files, 'manage_upload')
+    def manage_upload(self,file='',REQUEST=None):
+        """
+        Replaces the current contents of the File or Image object with file.
+
+        The file or images contents are replaced with the contents of 'file'.
+        """
+        if self.wl_isLocked():
+            raise ResourceLockedError, "File is locked via WebDAV"
+
+        content_type=self._get_content_type(file, self.__name__,
+                                            'application/octet-stream')
+        self.update_data(file, content_type)
+        notify(ObjectModifiedEvent(self))
+
+        if REQUEST:
+            message="Saved changes."
+            return self.manage_main(self,REQUEST,manage_tabs_message=message)
+
+    def _get_content_type(self, file, id, content_type=None):
+        headers=getattr(file, 'headers', None)
+        if headers and headers.has_key('content-type'):
+            content_type=headers['content-type']
+        else:
+            name = getattr(file, 'filename', self.uploaded_filename) or id
+            content_type, enc=guess_content_type(name, '', content_type)
+        return content_type
+
+    security.declareProtected(delete_objects, 'DELETE')
+
+    security.declareProtected(change_images_and_files, 'PUT')
+    def PUT(self, REQUEST, RESPONSE):
+        """Handle HTTP PUT requests"""
+        self.dav__init(REQUEST, RESPONSE)
+        self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
+        type=REQUEST.get_header('content-type', None)
+
+        file=REQUEST['BODYFILE']
+
+        content_type = self._get_content_type(file, self.__name__,
+                                              type or self.content_type)
+        self.update_data(file, content_type)
+
+        RESPONSE.setStatus(204)
+        return RESPONSE
+
+    security.declareProtected(View, 'get_size')
+    def get_size(self):
+        """Get the size of a file or image.
+
+        Returns the size of the file or image.
+        """
+        size=self.size
+        if size is None :
+            bf = self.open('r')
+            bf.seek(0,2)
+            self.size = size = bf.tell()
+            bf.close()
+        return size
+
+    # deprecated; use get_size!
+    getSize=get_size
+
+    security.declareProtected(View, 'getContentType')
+    def getContentType(self):
+        """Get the content type of a file or image.
+
+        Returns the content type (MIME type) of a file or image.
+        """
+        return self.content_type
+
+
+    def __str__(self): return str(self.data)
+    def __len__(self): return 1
+
+    security.declareProtected(ftp_access, 'manage_FTPstat')
+    security.declareProtected(ftp_access, 'manage_FTPlist')
+
+    security.declareProtected(ftp_access, 'manage_FTPget')
+    def manage_FTPget(self):
+        """Return body for ftp."""
+        RESPONSE = self.REQUEST.RESPONSE
+
+        if self.ZCacheable_isCachingEnabled():
+            result = self.ZCacheable_get(default=None)
+            if result is not None:
+                # We will always get None from RAMCacheManager but we will get
+                # something implementing the IStreamIterator interface
+                # from FileCacheManager.
+                # the content-length is required here by HTTPResponse, even
+                # though FTP doesn't use it.
+                RESPONSE.setHeader('Content-Length', self.size)
+                return result
+
+        bf = self.open('r')
+        data = bf.read()
+        bf.close()
+        RESPONSE.setBase(None)
+        return data
 
 manage_addImageForm=DTMLFile('dtml/imageAdd',globals(),
-							 Kind='Image',kind='image')
+                             Kind='Image',kind='image')
 def manage_addImage(self, id, file, title='', precondition='', content_type='',
-					REQUEST=None):
-	"""
-	Add a new Image object.
+                    REQUEST=None):
+    """
+    Add a new Image object.
 
-	Creates a new Image object 'id' with the contents of 'file'.
-	"""
+    Creates a new Image object 'id' with the contents of 'file'.
+    """
 
-	id=str(id)
-	title=str(title)
-	content_type=str(content_type)
-	precondition=str(precondition)
+    id=str(id)
+    title=str(title)
+    content_type=str(content_type)
+    precondition=str(precondition)
 
-	id, title = cookId(id, title, file)
+    id, title = cookId(id, title, file)
 
-	self=self.this()
-	self._setObject(id, Image(id,title,file,content_type, precondition))
+    self=self.this()
+    self._setObject(id, Image(id,title,file,content_type, precondition))
 
-	if REQUEST is not None:
-		try:	url=self.DestinationURL()
-		except: url=REQUEST['URL1']
-		REQUEST.RESPONSE.redirect('%s/manage_main' % url)
-	return id
+    newFile = self._getOb(id)
+    notify(ObjectCreatedEvent(newFile))
+    
+    if REQUEST is not None:
+        try:    url=self.DestinationURL()
+        except: url=REQUEST['URL1']
+        REQUEST.RESPONSE.redirect('%s/manage_main' % url)
+    return id
 
 
 def getImageInfo(file):
-	height = -1
-	width = -1
-	content_type = ''
-
-	# handle GIFs
-	data = file.read(24)
-	size = len(data)
-	if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
-		# Check to see if content_type is correct
-		content_type = 'image/gif'
-		w, h = struct.unpack("<HH", data[6:10])
-		width = int(w)
-		height = int(h)
-
-	# See PNG v1.2 spec (http://www.cdrom.com/pub/png/spec/)
-	# Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
-	# and finally the 4-byte width, height
-	elif ((size >= 24) and (data[:8] == '\211PNG\r\n\032\n')
-		  and (data[12:16] == 'IHDR')):
-		content_type = 'image/png'
-		w, h = struct.unpack(">LL", data[16:24])
-		width = int(w)
-		height = int(h)
-
-	# Maybe this is for an older PNG version.
-	elif (size >= 16) and (data[:8] == '\211PNG\r\n\032\n'):
-		# Check to see if we have the right content type
-		content_type = 'image/png'
-		w, h = struct.unpack(">LL", data[8:16])
-		width = int(w)
-		height = int(h)
-
-	# handle JPEGs
-	elif (size >= 2) and (data[:2] == '\377\330'):
-		content_type = 'image/jpeg'
-		jpeg = file
-		jpeg.seek(0)
-		jpeg.read(2)
-		b = jpeg.read(1)
-		try:
-			while (b and ord(b) != 0xDA):
-				while (ord(b) != 0xFF): b = jpeg.read(1)
-				while (ord(b) == 0xFF): b = jpeg.read(1)
-				if (ord(b) >= 0xC0 and ord(b) <= 0xC3):
-					jpeg.read(3)
-					h, w = struct.unpack(">HH", jpeg.read(4))
-					break
-				else:
-					jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2)
-				b = jpeg.read(1)
-			width = int(w)
-			height = int(h)
-		except: pass
-
-	return content_type, width, height
+    height = -1
+    width = -1
+    content_type = ''
+
+    # handle GIFs
+    data = file.read(24)
+    size = len(data)
+    if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
+        # Check to see if content_type is correct
+        content_type = 'image/gif'
+        w, h = struct.unpack("<HH", data[6:10])
+        width = int(w)
+        height = int(h)
+
+    # See PNG v1.2 spec (http://www.cdrom.com/pub/png/spec/)
+    # Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
+    # and finally the 4-byte width, height
+    elif ((size >= 24) and (data[:8] == '\211PNG\r\n\032\n')
+          and (data[12:16] == 'IHDR')):
+        content_type = 'image/png'
+        w, h = struct.unpack(">LL", data[16:24])
+        width = int(w)
+        height = int(h)
+
+    # Maybe this is for an older PNG version.
+    elif (size >= 16) and (data[:8] == '\211PNG\r\n\032\n'):
+        # Check to see if we have the right content type
+        content_type = 'image/png'
+        w, h = struct.unpack(">LL", data[8:16])
+        width = int(w)
+        height = int(h)
+
+    # handle JPEGs
+    elif (size >= 2) and (data[:2] == '\377\330'):
+        content_type = 'image/jpeg'
+        jpeg = file
+        jpeg.seek(0)
+        jpeg.read(2)
+        b = jpeg.read(1)
+        try:
+            while (b and ord(b) != 0xDA):
+                while (ord(b) != 0xFF): b = jpeg.read(1)
+                while (ord(b) == 0xFF): b = jpeg.read(1)
+                if (ord(b) >= 0xC0 and ord(b) <= 0xC3):
+                    jpeg.read(3)
+                    h, w = struct.unpack(">HH", jpeg.read(4))
+                    break
+                else:
+                    jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2)
+                b = jpeg.read(1)
+            width = int(w)
+            height = int(h)
+        except: pass
+
+    return content_type, width, height
 
 
 class Image(File):
-	"""Image objects can be GIF, PNG or JPEG and have the same methods
-	as File objects.  Images also have a string representation that
-	renders an HTML 'IMG' tag.
-	"""
-	__implements__ = (WriteLockInterface,)
-	meta_type='Blob Image'
-
-	security = ClassSecurityInfo()
-	security.declareObjectProtected(View)
-
-	alt=''
-	height=''
-	width=''
-
-	# FIXME: Redundant, already in base class
-	security.declareProtected(change_images_and_files, 'manage_edit')
-	security.declareProtected(change_images_and_files, 'manage_upload')
-	security.declareProtected(change_images_and_files, 'PUT')
-	security.declareProtected(View, 'index_html')
-	security.declareProtected(View, 'get_size')
-	security.declareProtected(View, 'getContentType')
-	security.declareProtected(ftp_access, 'manage_FTPstat')
-	security.declareProtected(ftp_access, 'manage_FTPlist')
-	security.declareProtected(ftp_access, 'manage_FTPget')
-	security.declareProtected(delete_objects, 'DELETE')
-
-	_properties=({'id':'title', 'type': 'string'},
-				 {'id':'alt', 'type':'string'},
-				 {'id':'content_type', 'type':'string','mode':'w'},
-				 {'id':'height', 'type':'string'},
-				 {'id':'width', 'type':'string'},
-				 )
-
-	manage_options=(
-		({'label':'Edit', 'action':'manage_main',
-		 'help':('OFSP','Image_Edit.stx')},
-		 {'label':'View', 'action':'view_image_or_file',
-		 'help':('OFSP','Image_View.stx')},)
-		+ PropertyManager.manage_options
-		+ RoleManager.manage_options
-		+ Item_w__name__.manage_options
-		+ Cacheable.manage_options
-		)
-
-	manage_editForm	 =DTMLFile('dtml/imageEdit',globals(),
-							   Kind='Image',kind='image')
-	manage_editForm._setName('manage_editForm')
-
-	security.declareProtected(View, 'view_image_or_file')
-	view_image_or_file =DTMLFile('dtml/imageView',globals())
-
-	security.declareProtected(view_management_screens, 'manage')
-	security.declareProtected(view_management_screens, 'manage_main')
-	manage=manage_main=manage_editForm
-	manage_uploadForm=manage_editForm
-	
-	security.declarePrivate('update_data')
-	def update_data(self, file, content_type=None):
-		super(Image, self).update_data(file, content_type)
-		self.updateFormat(size=self.size, content_type=content_type)
-		
-	security.declarePrivate('updateFormat')
-	def updateFormat(self, size=None, dimensions=None, content_type=None):
-		self.updateSize(size=size)
-
-		if dimensions is None or content_type is None :
-			bf = self.open('r')
-			ct, width, height = getImageInfo(bf)
-			bf.close()
-			if ct:
-				content_type = ct
-			if width >= 0 and height >= 0:
-				self.width = width
-				self.height = height
-
-			# Now we should have the correct content type, or still None
-			if content_type is not None: self.content_type = content_type
-		else :
-			self.width, self.height = dimensions
-			self.content_type = content_type
-
-	def __str__(self):
-		return self.tag()
-
-	security.declareProtected(View, 'tag')
-	def tag(self, height=None, width=None, alt=None,
-			scale=0, xscale=0, yscale=0, css_class=None, title=None, **args):
-		"""
-		Generate an HTML IMG tag for this image, with customization.
-		Arguments to self.tag() can be any valid attributes of an IMG tag.
-		'src' will always be an absolute pathname, to prevent redundant
-		downloading of images. Defaults are applied intelligently for
-		'height', 'width', and 'alt'. If specified, the 'scale', 'xscale',
-		and 'yscale' keyword arguments will be used to automatically adjust
-		the output height and width values of the image tag.
-
-		Since 'class' is a Python reserved word, it cannot be passed in
-		directly in keyword arguments which is a problem if you are
-		trying to use 'tag()' to include a CSS class. The tag() method
-		will accept a 'css_class' argument that will be converted to
-		'class' in the output tag to work around this.
-		"""
-		if height is None: height=self.height
-		if width is None:  width=self.width
-
-		# Auto-scaling support
-		xdelta = xscale or scale
-		ydelta = yscale or scale
-
-		if xdelta and width:
-			width =	 str(int(round(int(width) * xdelta)))
-		if ydelta and height:
-			height = str(int(round(int(height) * ydelta)))
-
-		result='<img src="%s"' % (self.absolute_url())
-
-		if alt is None:
-			alt=getattr(self, 'alt', '')
-		result = '%s alt="%s"' % (result, escape(alt, 1))
-
-		if title is None:
-			title=getattr(self, 'title', '')
-		result = '%s title="%s"' % (result, escape(title, 1))
-
-		if height:
-			result = '%s height="%s"' % (result, height)
-
-		if width:
-			result = '%s width="%s"' % (result, width)
-
-		# Omitting 'border' attribute (Collector #1557)
-#		 if not 'border' in [ x.lower() for x in  args.keys()]:
-#			 result = '%s border="0"' % result
-
-		if css_class is not None:
-			result = '%s class="%s"' % (result, css_class)
-
-		for key in args.keys():
-			value = args.get(key)
-			if value:
-				result = '%s %s="%s"' % (result, key, value)
-
-		return '%s />' % result
+    """Image objects can be GIF, PNG or JPEG and have the same methods
+    as File objects.  Images also have a string representation that
+    renders an HTML 'IMG' tag.
+    """
+    meta_type='Blob Image'
+
+    security = ClassSecurityInfo()
+    security.declareObjectProtected(View)
+
+    alt=''
+    height=''
+    width=''
+
+    # FIXME: Redundant, already in base class
+    security.declareProtected(change_images_and_files, 'manage_edit')
+    security.declareProtected(change_images_and_files, 'manage_upload')
+    security.declareProtected(change_images_and_files, 'PUT')
+    security.declareProtected(View, 'index_html')
+    security.declareProtected(View, 'get_size')
+    security.declareProtected(View, 'getContentType')
+    security.declareProtected(ftp_access, 'manage_FTPstat')
+    security.declareProtected(ftp_access, 'manage_FTPlist')
+    security.declareProtected(ftp_access, 'manage_FTPget')
+    security.declareProtected(delete_objects, 'DELETE')
+
+    _properties=({'id':'title', 'type': 'string'},
+                 {'id':'alt', 'type':'string'},
+                 {'id':'content_type', 'type':'string','mode':'w'},
+                 {'id':'height', 'type':'string'},
+                 {'id':'width', 'type':'string'},
+                 )
+
+    manage_options=(
+        ({'label':'Edit', 'action':'manage_main',
+         'help':('OFSP','Image_Edit.stx')},
+         {'label':'View', 'action':'view_image_or_file',
+         'help':('OFSP','Image_View.stx')},)
+        + PropertyManager.manage_options
+        + RoleManager.manage_options
+        + Item_w__name__.manage_options
+        + Cacheable.manage_options
+        )
+
+    manage_editForm  =DTMLFile('dtml/imageEdit',globals(),
+                               Kind='Image',kind='image')
+    manage_editForm._setName('manage_editForm')
+
+    security.declareProtected(View, 'view_image_or_file')
+    view_image_or_file =DTMLFile('dtml/imageView',globals())
+
+    security.declareProtected(view_management_screens, 'manage')
+    security.declareProtected(view_management_screens, 'manage_main')
+    manage=manage_main=manage_editForm
+    manage_uploadForm=manage_editForm
+
+    security.declarePrivate('update_data')
+    def update_data(self, file, content_type=None):
+        super(Image, self).update_data(file, content_type)
+        self.updateFormat(size=self.size, content_type=content_type)
+        
+    security.declarePrivate('updateFormat')
+    def updateFormat(self, size=None, dimensions=None, content_type=None):
+        self.updateSize(size=size)
+
+        if dimensions is None or content_type is None :
+            bf = self.open('r')
+            ct, width, height = getImageInfo(bf)
+            bf.close()
+            if ct:
+                content_type = ct
+            if width >= 0 and height >= 0:
+                self.width = width
+                self.height = height
+
+            # Now we should have the correct content type, or still None
+            if content_type is not None: self.content_type = content_type
+        else :
+            self.width, self.height = dimensions
+            self.content_type = content_type
+
+    def __str__(self):
+        return self.tag()
+
+    security.declareProtected(View, 'tag')
+    def tag(self, height=None, width=None, alt=None,
+            scale=0, xscale=0, yscale=0, css_class=None, title=None, **args):
+        """
+        Generate an HTML IMG tag for this image, with customization.
+        Arguments to self.tag() can be any valid attributes of an IMG tag.
+        'src' will always be an absolute pathname, to prevent redundant
+        downloading of images. Defaults are applied intelligently for
+        'height', 'width', and 'alt'. If specified, the 'scale', 'xscale',
+        and 'yscale' keyword arguments will be used to automatically adjust
+        the output height and width values of the image tag.
+
+        Since 'class' is a Python reserved word, it cannot be passed in
+        directly in keyword arguments which is a problem if you are
+        trying to use 'tag()' to include a CSS class. The tag() method
+        will accept a 'css_class' argument that will be converted to
+        'class' in the output tag to work around this.
+        """
+        if height is None: height=self.height
+        if width is None:  width=self.width
+
+        # Auto-scaling support
+        xdelta = xscale or scale
+        ydelta = yscale or scale
+
+        if xdelta and width:
+            width =  str(int(round(int(width) * xdelta)))
+        if ydelta and height:
+            height = str(int(round(int(height) * ydelta)))
+
+        result='<img src="%s"' % (self.absolute_url())
+
+        if alt is None:
+            alt=getattr(self, 'alt', '')
+        result = '%s alt="%s"' % (result, escape(alt, 1))
+
+        if title is None:
+            title=getattr(self, 'title', '')
+        result = '%s title="%s"' % (result, escape(title, 1))
+
+        if height:
+            result = '%s height="%s"' % (result, height)
+
+        if width:
+            result = '%s width="%s"' % (result, width)
+
+        # Omitting 'border' attribute (Collector #1557)
+#        if not 'border' in [ x.lower() for x in  args.keys()]:
+#            result = '%s border="0"' % result
+
+        if css_class is not None:
+            result = '%s class="%s"' % (result, css_class)
+
+        for key in args.keys():
+            value = args.get(key)
+            if value:
+                result = '%s %s="%s"' % (result, key, value)
+
+        return '%s />' % result
 
 
 def cookId(id, title, file):
-	if not id and hasattr(file,'filename'):
-		filename=file.filename
-		title=title or filename
-		id=filename[max(filename.rfind('/'),
-						filename.rfind('\\'),
-						filename.rfind(':'),
-						)+1:]
-	return id, title
-
-#class Pdata(Persistent, Implicit):
-#	# Wrapper for possibly large data
-#
-#	next=None
-#
-#	def __init__(self, data):
-#		self.data=data
-#
-#	def __getslice__(self, i, j):
-#		return self.data[i:j]
-#
-#	def __len__(self):
-#		data = str(self)
-#		return len(data)
-#
-#	def __str__(self):
-#		next=self.next
-#		if next is None: return self.data
-#
-#		r=[self.data]
-#		while next is not None:
-#			self=next
-#			r.append(self.data)
-#			next=self.next
-#
-#		return ''.join(r)
+    if not id and hasattr(file,'filename'):
+        filename=file.filename
+        title=title or filename
+        id=filename[max(filename.rfind('/'),
+                        filename.rfind('\\'),
+                        filename.rfind(':'),
+                        )+1:]
+    return id, title