source: ZMS/trunk/_blobfields.py @ 1761

Revision 1761, 41.4 KB checked in by zmsdev, 8 weeks ago (diff)

applied minor performance-fixes (2)

Line 
1################################################################################
2# _blobfields.py
3#
4# This program is free software; you can redistribute it and/or
5# modify it under the terms of the GNU General Public License
6# as published by the Free Software Foundation; either version 2
7# of the License, or (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17################################################################################
18
19# Imports.
20from webdav.common import rfc1123_date
21from DateTime.DateTime import DateTime
22from ZPublisher import HTTPRangeSupport
23from OFS.CopySupport import absattr
24from OFS.Image import Image, File
25from cStringIO import StringIO
26from mimetools import choose_boundary
27from types import StringTypes
28import copy
29import urllib
30import warnings
31import zExceptions
32# Product Imports.
33import _fileutil
34import _globals
35import _mimetypes
36import _pilutil
37
38__all__= ['MyBlob','MyImage','MyFile']
39
40
41"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
42_blobfields.StringType:
43"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
44StringType=type('')
45
46
47"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
48_blobfields.recurse_downloadRessources:
49
50Download from ZODB to file-system during Export.
51"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
52def recurse_downloadRessources(self, base_path, REQUEST, incl_embedded):
53  ressources = []
54  # Check Constraints.
55  root = getattr( self, '__root__', None)
56  if root is not None:
57    return ressources
58  ob = self
59  if ob.meta_id == 'ZMSLinkElement' and ob.isEmbedded( REQUEST) and incl_embedded:
60    ob = ob.getRefObj()
61  # Attributes.
62  langs = ob.getLangIds()
63  prim_lang = ob.getPrimaryLanguage()
64  obj_attrs = ob.getObjAttrs()
65  for key in obj_attrs.keys():
66    obj_attr = ob.getObjAttr(key)
67    datatype = obj_attr['datatype_key']
68    if datatype in _globals.DT_BLOBS:
69      for lang in langs:
70        try:
71          if obj_attr['multilang'] or lang==prim_lang or (obj_attr['multilang']==0 and lang!=prim_lang):
72            req = {'lang':lang,'preview':'preview'}
73            obj_vers = ob.getObjVersion(req)
74            blob = ob._getObjAttrValue(obj_attr,obj_vers,lang)
75            if blob is not None:
76              filename = blob.getFilename()
77              filename = getLangFilename(ob,filename,lang)
78              filename = '%s%s'%(base_path,filename)
79              filename = _fileutil.getOSPath(filename)
80              _fileutil.exportObj(blob,filename)
81              ressources.append( { 'filepath':filename, 'content_type':blob.getContentType()})
82        except:
83          _globals.writeError(ob,"[recurse_downloadRessources]: Can't export %s"%key)
84    elif datatype == _globals.DT_LIST:
85      for lang in langs:
86        try:
87          if obj_attr['multilang'] or lang==prim_lang or (obj_attr['multilang']==0 and lang!=prim_lang):
88            req = {'lang':lang,'preview':'preview'}
89            obj_vers = ob.getObjVersion(req)
90            v = ob._getObjAttrValue(obj_attr,obj_vers,lang)
91            i = 0
92            for r in v:
93              uu = []
94              if type(r) is dict:
95                for k in r.keys():
96                  u = r[k]
97                  if isinstance(u,MyImage) or isinstance(u,MyFile):
98                    uu.append( u)
99              elif isinstance(r,MyImage) or isinstance(r,MyFile):
100                uu.append( r)
101              for u in uu:
102                filename = u.getFilename()
103                filename = getLangFilename(ob,filename,lang)
104                filename = '%s@%i/%s'%(base_path,i,filename)
105                filename = _fileutil.getOSPath(filename)
106                _fileutil.exportObj(u,filename)
107                ressources.append( { 'filepath':filename, 'content_type':u.getContentType()})
108              i = i + 1
109        except:
110          _globals.writeError(ob,"[recurse_downloadRessources]: Can't export %s"%key)
111  # Process children.
112  for child in ob.getChildNodes():
113    ressources.extend( recurse_downloadRessources( child, base_path+child.id+'/', REQUEST, incl_embedded))
114  # Return list of ressources.
115  return ressources
116
117
118"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
119_blobfields.recurse_uploadRessources:
120
121Upload from file-system to ZODB during Import.
122"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
123def recurse_uploadRessources(self, folder='.', mediadbStorable=True):
124  message = ''
125  # Upload blob-fields.
126  uploadRessources(self,folder,mediadbStorable)
127  # Process children.
128  for child in self.getChildNodes():
129    message += recurse_uploadRessources(child,folder,mediadbStorable)
130  # Return message.
131  return message
132
133
134"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
135_blobfields.uploadRessources:
136
137Upload blob-fields from file-system to ZODB during import.
138"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
139def uploadRessources(self, folder='.', mediadbStorable=True):
140  langs = self.getLangIds()
141  prim_lang = self.getPrimaryLanguage()
142  obj_attrs = self.getObjAttrs()
143  for key in obj_attrs.keys():
144    obj_attr = self.getObjAttr(key)
145    datatype = obj_attr['datatype_key']
146    if datatype in _globals.DT_BLOBS:
147      for lang in langs:
148        try:
149          if obj_attr['multilang'] or lang==prim_lang:
150            req = {'lang':lang,'preview':'preview'}
151            obj_vers = self.getObjVersion(req)
152            blob = self._getObjAttrValue(obj_attr,obj_vers,lang)
153            if blob is not None:
154              filename = _fileutil.getOSPath('%s/%s'%(folder,blob.filename))
155              _globals.writeBlock( self, '[uploadRessources]: filename=%s'%filename)
156              # Backup properties (otherwise manage_upload sets it).
157              bk = {}
158              for __xml_attr__ in blob.__xml_attrs__:
159                bk[__xml_attr__] = getattr(blob,__xml_attr__,'')
160              # Read file to ZODB.
161              f = open( filename, 'rb')
162              try:
163                blob = createBlobField( self, datatype, file={'data':f,'filename':filename})
164              finally:
165                f.close()
166              # Restore properties.
167              for __xml_attr__ in blob.__xml_attrs__:
168                if bk.get(__xml_attr__,'') not in ['','text/x-unknown-content-type']:
169                  setattr(blob,__xml_attr__,bk[__xml_attr__])
170              blob.getFilename() # Normalize filename
171              self.setObjProperty(key,blob,lang)
172        except:
173          _globals.writeError(self,"[uploadRessources]")
174
175
176"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
177_blobfields.createBlobField:
178
179Create blob-field of desired object-type and initialize it with given file.
180
181IN:    objtype        [DT_IMAGE|DT_FILE]
182  file        [ZPublisher.HTTPRequest.FileUpload|dictionary]
183     mediadbStorable    [True|False]
184OUT:    blob        [MyImage|MyFile]
185"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
186def createBlobField(self, objtype, file='', mediadbStorable=True):
187  if type( file) in StringTypes:
188    blob = uploadBlobField( self, objtype, file)
189  elif type( file) is dict:
190    data = file.get( 'data', '')
191    if type( data) is StringType:
192      data = StringIO( data)
193    blob = uploadBlobField( self, objtype, data, file.get('filename',''), mediadbStorable)
194    if file.get('content_type'):
195      blob.content_type = file.get('content_type')
196  else:
197    blob = uploadBlobField( self, objtype, file, file.filename, mediadbStorable)
198  return blob
199
200
201"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
202_blobfields.uploadBlobField
203"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
204def uploadBlobField(self, objtype, file='', filename='', mediadbStorable=True):
205  if objtype == _globals.DT_IMAGE:
206    clazz = MyImage
207    maxlength_prop = 'ZMS.input.image.maxlength'
208  elif objtype == _globals.DT_FILE:
209    clazz = MyFile
210    maxlength_prop = 'ZMS.input.file.maxlength'
211  blob = clazz( id='',title='',file=file)
212  blob.aq_parent = self
213  blob.mediadbfile = None
214  blob.filename = _fileutil.extractFilename( filename, undoable=True).encode('utf-8')
215  # Check size.
216  if self is not None:
217    maxlength = self.getConfProperty(maxlength_prop,'')
218    if len(maxlength) > 0:
219      size = blob.get_size()
220      if size > int(maxlength):
221        raise zExceptions.Forbidden('size=%i > %s=%i' %(size,maxlength_prop,int(maxlength)))
222  # Store data in media-db.
223  if self is not None and mediadbStorable:
224    mediadb = self.getMediaDb()
225    if mediadb is not None:
226      blob.mediadbfile = mediadb.storeFile( blob)
227      blob.data = ''
228  return blob
229
230
231"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
232_blobfields.getLangFilename:
233 
234Returns filename concatenated with language suffix.
235"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
236def getLangFilename(self, filename, lang):
237  i = filename.rfind('.')
238  name = filename[:i]
239  name = name.replace(' ','_')
240  ext = filename[i+1:]
241  if len(self.getLangIds()) > 1 and lang is not None:
242    suffix = '_' + lang
243    if len(name) < len(suffix) or name[-len(suffix):] != suffix:
244      name += suffix
245  name += '.' + ext
246  return name
247
248
249################################################################################
250###
251###  THUMBNAILS
252###
253################################################################################
254
255"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
256_blobfields.thumbnailImageFields:
257
258Process image-fields and shrink superres to hires and hires to lores.
259"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
260def thumbnailImageFields(self, lang, REQUEST):
261  message = ''
262  if self.pilutil().enabled():
263    obj_attrs = self.getObjAttrs()
264    for key in obj_attrs.keys():
265      obj_attr = self.getObjAttr(key)
266      datatype = obj_attr['datatype_key']
267      if datatype == _globals.DT_IMAGE:
268        message += thumbnailImage(self,'%ssuperres'%key,'%shires'%key,self.getConfProperty('InstalledProducts.pil.hires.thumbnail.max',600),lang,REQUEST)
269        message += thumbnailImage(self,'%shires'%key,'%s'%key,self.getConfProperty('InstalledProducts.pil.thumbnail.max',100),lang,REQUEST)
270  return message
271
272
273"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
274_blobfields.thumbnailImage:
275
276Process image-field and shrink attribute given by hiresKey to attribute given
277by loresKey.
278"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
279def thumbnailImage(self, hiresKey, loresKey, maxdim, lang, REQUEST):
280  message = ''
281  try:
282    if hiresKey in self.getObjAttrs().keys():
283      req = {'lang':lang,'preview':'preview','fetchReqBuff':False}
284      hiresImg = self.getObjProperty(hiresKey,req)
285      if hiresImg is not None and REQUEST.get('generate_preview_%s_%s'%(hiresKey,lang),0) == 1:
286        _globals.writeLog( self, '[thumbnailImage]: Create >%s< from >%s<...'%(loresKey,hiresKey))
287        thumb = self.pilutil().thumbnail( hiresImg, maxdim)
288        self.setObjProperty(loresKey,thumb,lang)
289  except:
290    _globals.writeError( self, '[thumbnailImage]')
291  return message
292
293
294################################################################################
295################################################################################
296###
297###   Class
298###
299################################################################################
300################################################################################
301class MyBlob:
302
303    # Documentation string.
304    __doc__ = """ZMS product module."""
305    # Version string.
306    __version__ = '0.1'
307   
308
309    __class_name__ = '{{MyBlob}}'
310   
311    def __str__(self):
312      return self.__class_name__
313
314
315    # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
316    #  <BO> Copied from OFS/Image.py:
317    #  Modifications: self._p_mtime -> self.aq_parent._p_mtime
318    # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
319
320    def _if_modified_since_request_handler(self, REQUEST, RESPONSE):
321        # HTTP If-Modified-Since header handling: return True if
322        # we can handle this request by returning a 304 response
323        header=REQUEST.get_header('If-Modified-Since', None)
324        if header is not None:
325            header=header.split( ';')[0]
326            # Some proxies seem to send invalid date strings for this
327            # header. If the date string is not valid, we ignore it
328            # rather than raise an error to be generally consistent
329            # with common servers such as Apache (which can usually
330            # understand the screwy date string as a lucky side effect
331            # of the way they parse it).
332            # This happens to be what RFC2616 tells us to do in the face of an
333            # invalid date.
334            try:    mod_since=long(DateTime(header).timeTime())
335            except: mod_since=None
336            if mod_since is not None:
337                if self.aq_parent._p_mtime:
338                    last_mod = long(self.aq_parent._p_mtime)
339                else:
340                    last_mod = long(0)
341                if last_mod > 0 and last_mod <= mod_since:
342                    RESPONSE.setHeader('Last-Modified',
343                                       rfc1123_date(self.aq_parent._p_mtime))
344                    RESPONSE.setHeader('Content-Type', self.content_type)
345                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
346                    RESPONSE.setStatus(304)
347                    return True
348
349    def _range_request_handler(self, REQUEST, RESPONSE):
350        # HTTP Range header handling: return True if we've served a range
351        # chunk out of our data.
352        range = REQUEST.get_header('Range', None)
353        request_range = REQUEST.get_header('Request-Range', None)
354        if request_range is not None:
355            # Netscape 2 through 4 and MSIE 3 implement a draft version
356            # Later on, we need to serve a different mime-type as well.
357            range = request_range
358        if_range = REQUEST.get_header('If-Range', None)
359        if range is not None:
360            ranges = HTTPRangeSupport.parseRange(range)
361           
362            if if_range is not None:
363                # Only send ranges if the data isn't modified, otherwise send
364                # the whole object. Support both ETags and Last-Modified dates!
365                if len(if_range) > 1 and if_range[:2] == 'ts':
366                    # ETag:
367                    if if_range != self.http__etag():
368                        # Modified, so send a normal response. We delete
369                        # the ranges, which causes us to skip to the 200
370                        # response.
371                        ranges = None
372                else:
373                    # Date
374                    date = if_range.split( ';')[0]
375                    try: mod_since=long(DateTime(date).timeTime())
376                    except: mod_since=None
377                    if mod_since is not None:
378                        if self.aq_parent._p_mtime:
379                            last_mod = long(self.aq_parent._p_mtime)
380                        else:
381                            last_mod = long(0)
382                        if last_mod > mod_since:
383                            # Modified, so send a normal response. We delete
384                            # the ranges, which causes us to skip to the 200
385                            # response.
386                            ranges = None
387
388            if ranges:
389                # Search for satisfiable ranges.
390                satisfiable = 0
391                for start, end in ranges:
392                    if start < self.size:
393                        satisfiable = 1
394                        break
395
396                if not satisfiable:
397                    RESPONSE.setHeader('Content-Range',
398                        'bytes */%d' % self.size)
399                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
400                    RESPONSE.setHeader('Last-Modified',
401                        rfc1123_date(self.aq_parent._p_mtime))
402                    RESPONSE.setHeader('Content-Type', self.content_type)
403                    RESPONSE.setHeader('Content-Length', self.size)
404                    RESPONSE.setStatus(416)
405                    return True
406
407                ranges = HTTPRangeSupport.expandRanges(ranges, self.size)
408
409                if len(ranges) == 1:
410                    # Easy case, set extra header and return partial set.
411                    start, end = ranges[0]
412                    size = end - start
413
414                    RESPONSE.setHeader('Last-Modified',
415                        rfc1123_date(self.aq_parent._p_mtime))
416                    RESPONSE.setHeader('Content-Type', self.content_type)
417                    RESPONSE.setHeader('Content-Length', size)
418                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
419                    RESPONSE.setHeader('Content-Range',
420                        'bytes %d-%d/%d' % (start, end - 1, self.size))
421                    RESPONSE.setStatus(206) # Partial content
422
423                    data = self.data
424                    if type(data) is StringType:
425                        RESPONSE.write(data[start:end])
426                        return True
427
428                    # Linked Pdata objects. Urgh.
429                    pos = 0
430                    while data is not None:
431                        l = len(data.data)
432                        pos = pos + l
433                        if pos > start:
434                            # We are within the range
435                            lstart = l - (pos - start)
436
437                            if lstart < 0: lstart = 0
438
439                            # find the endpoint
440                            if end <= pos:
441                                lend = l - (pos - end)
442
443                                # Send and end transmission
444                                RESPONSE.write(data[lstart:lend])
445                                break
446
447                            # Not yet at the end, transmit what we have.
448                            RESPONSE.write(data[lstart:])
449
450                        data = data.next
451
452                    return True
453
454                else:
455                    boundary = choose_boundary()
456
457                    # Calculate the content length
458                    size = (8 + len(boundary) + # End marker length
459                        len(ranges) * (         # Constant lenght per set
460                            49 + len(boundary) + len(self.content_type) +
461                            len('%d' % self.size)))
462                    for start, end in ranges:
463                        # Variable length per set
464                        size = (size + len('%d%d' % (start, end - 1)) +
465                            end - start)
466
467
468                    # Some clients implement an earlier draft of the spec, they
469                    # will only accept x-byteranges.
470                    draftprefix = (request_range is not None) and 'x-' or ''
471
472                    RESPONSE.setHeader('Content-Length', size)
473                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
474                    RESPONSE.setHeader('Last-Modified',
475                        rfc1123_date(self.aq_parent._p_mtime))
476                    RESPONSE.setHeader('Content-Type',
477                        'multipart/%sbyteranges; boundary=%s' % (
478                            draftprefix, boundary))
479                    RESPONSE.setStatus(206) # Partial content
480
481                    data = self.data
482                    # The Pdata map allows us to jump into the Pdata chain
483                    # arbitrarily during out-of-order range searching.
484                    pdata_map = {}
485                    pdata_map[0] = data
486
487                    for start, end in ranges:
488                        RESPONSE.write('\r\n--%s\r\n' % boundary)
489                        RESPONSE.write('Content-Type: %s\r\n' %
490                            self.content_type)
491                        RESPONSE.write(
492                            'Content-Range: bytes %d-%d/%d\r\n\r\n' % (
493                                start, end - 1, self.size))
494
495                        if type(data) is StringType:
496                            RESPONSE.write(data[start:end])
497
498                        else:
499                            # Yippee. Linked Pdata objects. The following
500                            # calculations allow us to fast-forward through the
501                            # Pdata chain without a lot of dereferencing if we
502                            # did the work already.
503                            first_size = len(pdata_map[0].data)
504                            if start < first_size:
505                                closest_pos = 0
506                            else:
507                                closest_pos = (
508                                    ((start - first_size) >> 16 << 16) +
509                                    first_size)
510                            pos = min(closest_pos, max(pdata_map.keys()))
511                            data = pdata_map[pos]
512
513                            while data is not None:
514                                l = len(data.data)
515                                pos = pos + l
516                                if pos > start:
517                                    # We are within the range
518                                    lstart = l - (pos - start)
519
520                                    if lstart < 0: lstart = 0
521
522                                    # find the endpoint
523                                    if end <= pos:
524                                        lend = l - (pos - end)
525
526                                        # Send and loop to next range
527                                        RESPONSE.write(data[lstart:lend])
528                                        break
529
530                                    # Not yet at the end, transmit what we have.
531                                    RESPONSE.write(data[lstart:])
532
533                                data = data.next
534                                # Store a reference to a Pdata chain link so we
535                                # don't have to deref during this request again.
536                                pdata_map[pos] = data
537
538                    # Do not keep the link references around.
539                    del pdata_map
540
541                    RESPONSE.write('\r\n--%s--\r\n' % boundary)
542                    return True
543
544    # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
545    #  <EO> Copied from OFS/Image.py
546    # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
547
548
549    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
550    MyBlob.equals
551    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
552    def equals(self, ob):
553      """
554      Indicates whether some other MyBlob-object is "equal to" this one.
555      """
556      b = ob is not None
557      try:
558        if b:
559          attrs = self.__obj_attrs__
560          for attr in attrs:
561            if b and attr != 'data':
562              b = getattr( self, attr) == getattr( ob, attr)
563      except:
564        b = False
565      return b
566
567
568    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
569    MyBlob._createCopy:
570    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
571    def _createCopy(self, aq_parent, key):
572      value = self._getCopy()
573      value.is_blob = True
574      value.aq_parent = aq_parent
575      value.base_url = aq_parent.base_url()
576      value.key = key
577      value.lang = aq_parent.REQUEST.get( 'lang')
578      return value
579
580
581    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
582    MyBlob.__call__:
583    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
584    def __bobo_traverse__(self, TraversalRequest, name):
585      return self
586
587
588    __call____roles__ = None
589    def __call__(self, REQUEST=None, **kw):
590      """"""
591      if REQUEST is not None and REQUEST.has_key('path_to_handle'):
592        REQUEST['path_to_handle']=[]
593        RESPONSE = REQUEST.RESPONSE
594        parent = self.aq_parent
595       
596        access = parent.hasAccess( REQUEST) or parent.getConfProperty( 'ZMS.blobfields.grant_public_access', 0) == 1
597        # Hook for custom access rules: return True/False, return 404 (Forbidden) if you want to perform redirect
598        if access:
599          try:
600            name = 'hasCustomAccess'
601            if hasattr(parent,name):
602              v = getattr(parent,name)(context=parent,REQUEST=REQUEST)
603              if type( v) is bool:
604                access = access and v
605              elif type( v) is int and v == 404:
606                return ''
607          except:
608            _globals.writeError(parent,'[__call__]: can\'t %s'%name)
609        # Raise unauthorized error.
610        if not access:
611          if getattr(parent,'login_form',None):
612            target = '%s/login_form'%parent.absolute_url()
613            target = parent.url_append_params(target,{'came_from':REQUEST.get('URL')})
614            if REQUEST.get('QUERY_STRING'):
615              target = parent.url_append_params(target,{'QUERY_STRING':REQUEST.get('QUERY_STRING')})
616            return RESPONSE.redirect(target)
617          else:
618            raise zExceptions.Unauthorized
619       
620        if self._if_modified_since_request_handler(REQUEST, RESPONSE):
621            # we were able to handle this by returning a 304 (not modified)
622            # unfortunately, because the HTTP cache manager uses the cache
623            # API, and because 304 (not modified) responses are required to carry the Expires
624            # header for HTTP/1.1, we need to call ZCacheable_set here.
625            # This is nonsensical for caches other than the HTTP cache manager
626            # unfortunately.
627            self.ZCacheable_set(None)
628            return ''
629
630        if isinstance(self,MyImage) and self._range_request_handler(REQUEST, RESPONSE):
631            # we served a chunk of content in response to a range request.
632            return ''
633
634        RESPONSE.setHeader('Last-Modified', rfc1123_date(parent._p_mtime))
635        RESPONSE.setHeader('Content-Type', self.content_type)
636        RESPONSE.setHeader('Content-Length', self.get_size())
637        RESPONSE.setHeader('Content-Disposition','inline;filename="%s"'%self.getFilename())
638        accept_ranges = parent.getConfProperty('ZMS.blobfields.accept_ranges','bytes')
639        if len( accept_ranges) > 0:
640          RESPONSE.setHeader('Accept-Ranges', accept_ranges)
641        cacheable = not REQUEST.get('preview') == 'preview'
642        if cacheable:
643          cacheable = parent.hasPublicAccess() or parent.isCachedPage( REQUEST)
644        # Hook for custom cacheable rules: return True/False
645        if cacheable:
646          try:
647            name = 'hasCustomPublicAccess'
648            if hasattr(parent,name):
649              v = getattr(parent,name)(context=parent,REQUEST=REQUEST)
650              if type( v) is bool:
651                cacheable = cacheable and v
652          except:
653            _globals.writeError(parent,'[__call__]: can\'t %s'%name)
654        if not cacheable:
655          RESPONSE.setHeader('Expires', '-1')
656          RESPONSE.setHeader('Cache-Control', 'no-cache')
657          # IE6 SSL Download bug:
658          # http://support.microsoft.com/kb/812935/en-us
659          # http://support.microsoft.com/kb/323308/en-us
660          if not REQUEST.get('URL','').startswith( 'https://'):
661            RESPONSE.setHeader('Pragma', 'no-cache')
662
663        # Hook for custom RESPONSE-headers
664        name = 'getCustomBlobResponseHeaders'
665        if hasattr(parent,name):
666          v = getattr(parent,name)(context=parent,REQUEST=REQUEST)
667       
668        if self.ZCacheable_isCachingEnabled():
669            result = self.ZCacheable_get(default=None)
670            if result is not None:
671                # We will always get None from RAMCacheManager and HTTP
672                # Accelerated Cache Manager but we will get
673                # something implementing the IStreamIterator interface
674                # from a "FileCacheManager"
675                return result
676       
677        self.ZCacheable_set(None)
678       
679        mediadb = parent.getMediaDb()
680        if mediadb is not None:
681          mediadbfile = self.getMediadbfile()
682          if mediadbfile is not None:
683            return mediadb.retrieveFileStreamIterator( mediadbfile, REQUEST)
684       
685        RESPONSE.setBase(None)
686        return self.getData()
687       
688      return self
689
690    index_html=None
691
692
693    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
694    MyBlob.getObjAttrs:
695    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
696    getObjAttrs__roles__ = None
697    def getObjAttrs(self, meta_type=None):
698      return {}
699
700
701    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
702    MyBlob.getData:
703    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
704    getData__roles__ = None
705    def getData(self, parent=None):
706      """
707      Returns data.
708      """
709      data = ''
710      mediadbfile = self.getMediadbfile()
711      if mediadbfile is not None:
712        if parent is None:
713          parent = self.aq_parent
714        mediadb = parent.getMediaDb()
715        if mediadb is not None:
716          try:
717            data = mediadb.retrieveFile( mediadbfile)
718          except:
719            _globals.writeError( parent, "[getData]: can't retrieve file from mediadb: %s"%str(mediadbfile))
720      else:
721        data = str(getattr(self,'data',''))
722      return data
723
724
725    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
726    MyBlob.getHref:
727    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
728    getHref__roles__ = None
729    def getHref(self, REQUEST):
730      """
731      Returns absolute url.
732      @var REQUEST: the triggering request
733      @type REQUEST: ZPublisher.HTTPRequest
734      """
735      parent = self.aq_parent
736      key = self.key
737      rownum = ''
738      i = key.find( ':')
739      if i > 0:
740        rownum = '/@%s'%key[ i+1:]
741      filename = getLangFilename( parent, self.getFilename(), self.lang)
742      filename = _globals.url_encode( filename)
743      qs = ''
744      zms_version_key = 'ZMS_VERSION_%s'%parent.id
745      if REQUEST.get( zms_version_key, None) is not None:
746        qs = _globals.qs_append( qs, zms_version_key,REQUEST.get( zms_version_key))
747      elif _globals.isPreviewRequest( REQUEST):
748        qs = _globals.qs_append( qs, 'preview', 'preview')
749      base_url = getattr( self, 'base_url', None)
750      if base_url is not None:
751        filename = base_url+rownum+'/'+filename
752      else:
753        try:
754          filename=parent.absolute_url()+rownum+'/'+filename
755        except:
756          filename=parent.id+rownum+'/'+filename
757      href = filename + qs
758      if (REQUEST.get('ZMS_PATHCROPPING',False) or parent.getConfProperty('ZMS.pathcropping',0)==1) and REQUEST.get('export_format','') == '':
759        base = REQUEST.get('BASE0','')
760        if href.find( base) == 0:
761          href = href[len(base):]
762      return href
763
764
765    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
766    MyBlob.getMediadbfile:
767    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
768    getMediadbfile__roles__ = None
769    def getMediadbfile(self):
770      """
771      Returns mediadb-filename.
772      @rtype: C{string}
773      """
774      return getattr(self,'mediadbfile',None)
775
776
777    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
778    MyBlob.getFilename:
779    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
780    getFilename__roles__ = None
781    def getFilename(self):
782      """
783      Returns filename.
784      @rtype: C{string}
785      """
786      filename = self.filename
787      while filename.startswith( '_'):
788        filename = filename[1:]
789      for ch in [ '+', '%', ' ', '!', '?', '#', '"', '(', ')','&' ]:
790        filename = filename.replace(ch,'')
791      if filename != self.filename and len( self.data) > 0:
792        self.filename = filename
793      return filename
794
795
796    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
797    MyBlob.get_real_size:
798    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
799    get_real_size__roles__ = None
800    def get_real_size(self):
801      """
802      Returns real size in ZODB.
803      @rtype: C{int}
804      """
805      if self.mediadbfile is None:
806        return self.get_size()
807      else:
808        return len(self.mediadbfile)
809
810
811    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
812    MyBlob.getDataSizeStr:
813    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
814    getDataSizeStr__roles__ = None
815    def getDataSizeStr(self):
816      """
817      Returns display string for file-size (KB).
818      @rtype: C{string}
819      @deprecated: Use ZMSGlobals.getDataSizeStr(len) instead!
820      """
821      warnings.warn('Using MyBlob.getDataSizeStr() is deprecated.'
822                   ' Use ZMSGlobals.getDataSizeStr(len) instead.',
823                     DeprecationWarning,
824                     stacklevel=2)
825      return _fileutil.getDataSizeStr(self.get_size())
826
827
828    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
829    MyBlob.getContentType:
830    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
831    getContentType__roles__ = None
832    def getContentType(self):
833      """
834      Returns MIME-type (e.g. image/gif, text/xml).
835      @rtype: C{string}
836      """
837      return self.content_type
838
839
840    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
841    MyBlob.getMimeTypeIconSrc:
842    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
843    getMimeTypeIconSrc__roles__ = None
844    def getMimeTypeIconSrc(self):
845      """
846      Returns the absolute-url of an icon representing the MIME-type of this MyBlob-object.
847      @rtype: C{string}
848      @deprecated: Use ZMSGlobals.getMimeTypeIconSrc(mt) instead!
849      """
850      warnings.warn('Using MyBlob.getMimeTypeIconSrc() is deprecated.'
851                   ' Use ZMSGlobals.getMimeTypeIconSrc(mt) instead.',
852                     DeprecationWarning,
853                     stacklevel=2)
854      return '/misc_/zms/' + _mimetypes.dctMimeType.get( self.getContentType(), _mimetypes.content_unknown)
855
856
857    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
858    MyBlob.xmlGetTagName:
859    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
860    def xmlGetTagName(self):
861      """
862      Returns <XML> Tag-Name.
863      @rtype: C{string}
864      """
865      return 'data'
866
867
868################################################################################
869################################################################################
870
871class MyImage(MyBlob,Image):
872
873    # Documentation string.
874    __doc__ = """ZMS product module."""
875    # Version string.
876    __version__ = '0.1'
877   
878
879    __obj_attrs__  = ['content_type','size','data','filename','mediadbfile','width','height']
880    __xml_attrs__  = ['content_type','width','height']
881
882
883    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
884    MyImage._getCopy:
885    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
886    def _getCopy(self):
887      self.getFilename() # Normalize filename
888      ob = self
889      clone = MyImage(id='',title='',file='')
890      attrs = self.__obj_attrs__
891      for attr in attrs:
892        if hasattr(ob,attr):
893          setattr(clone,attr,getattr(ob,attr))
894      return clone
895
896
897    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
898    MyImage.toXml:
899    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
900    def toXml(self, sender=None, base_path='', data2hex=True):
901      data = ''
902      objtype = ''
903      filename = _fileutil.getOSPath(_fileutil.extractFilename(getattr(self,'filename','')))
904      if data2hex:
905        if getattr(self,'content_type','').find('text/') == 0:
906          data = '<![CDATA[%s]]>'%str(self.getData( sender))
907        else:
908          data = _globals.bin2hex(self.getData( sender))
909        objtype = ' type="image"'
910      else:
911        filename = self.getFilename()
912        filename = getLangFilename(sender,filename,self.lang)
913        filename = '%s%s'%(base_path,filename)
914      xml = '\n<%s'%self.xmlGetTagName()
915      xml += ' width="%s"'%str(getattr(self,'width',''))
916      xml += ' height="%s"'%str(getattr(self,'height',''))
917      xml += ' content_type="%s"'%str(getattr(self,'content_type',''))
918      xml += ' filename="%s"'%filename
919      xml += objtype + '>' + data
920      xml += '</%s>'%self.xmlGetTagName()
921      return xml
922
923
924    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
925    MyImage.getWidth:
926    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
927    getWidth__roles__ = None
928    def getWidth(self):
929      return self.width
930
931
932    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
933    MyImage.getHeight:
934    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
935    getHeight__roles__ = None
936    def getHeight(self):
937      return self.height
938
939
940################################################################################
941################################################################################
942
943class MyFile(MyBlob,File):
944
945    # Documentation string.
946    __doc__ = """ZMS product module."""
947    # Version string.
948    __version__ = '0.1'
949
950
951    __obj_attrs__  = ['content_type','size','data','filename','mediadbfile']
952    __xml_attrs__  = ['content_type']
953    __class_name__ = '{{MyFile}}'
954
955    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
956    MyFile._getCopy:
957    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
958    def _getCopy(self):
959      self.getFilename() # Normalize filename
960      ob = self
961      clone = MyFile(id='',title='',file='')
962      attrs = self.__obj_attrs__
963      for attr in attrs:
964        if hasattr(ob,attr):
965          setattr(clone,attr,getattr(ob,attr))
966      return clone
967
968
969    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
970    MyFile.toXml:
971    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
972    def toXml(self, sender=None, base_path='', data2hex=True):
973      data = ''
974      objtype = ''
975      filename = _fileutil.getOSPath(_fileutil.extractFilename(getattr(self,'filename','')))
976      if data2hex:
977        if getattr(self,'content_type','').find('text/') == 0:
978          data = '<![CDATA[%s]]>'%str(self.getData( sender))
979        else:
980          data = _globals.bin2hex(self.getData( sender))
981        objtype = ' type="file"'
982      else:
983        filename = self.getFilename()
984        filename = getLangFilename(sender,filename,self.lang)
985        filename = '%s%s'%(base_path,filename)
986      xml = '\n<%s'%self.xmlGetTagName()
987      xml += ' content_type="%s"'%str(getattr(self,'content_type',''))
988      xml += ' filename="%s"'%filename
989      xml += objtype + '>' + data
990      xml += '</%s>'%self.xmlGetTagName()
991      return xml
992
993
994################################################################################
995################################################################################
996
997class MyBlobWrapper:
998
999    # Documentation string.
1000    __doc__ = """ZMS product module."""
1001    # Version string.
1002    __version__ = '0.1'
1003
1004    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1005    MyBlobWrapper.__init__:
1006   
1007    Constructor
1008    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1009    def __init__(self, f):
1010      self.f = f
1011
1012    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1013    MyBlobWrapper.getHref:
1014    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1015    getHref__roles__ = None
1016    def getHref(self, REQUEST):
1017      return self.f.absolute_url()
1018
1019    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1020    MyBlobWrapper.getFilename:
1021    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1022    getFilename__roles__ = None
1023    def getFilename(self):
1024      return absattr( self.f.id)
1025
1026    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1027    MyBlobWrapper.getData:
1028    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
1029    getData__roles__ = None
1030    def getData(self, parent=None):
1031      return self.f.data
1032
1033################################################################################
Note: See TracBrowser for help on using the repository browser.