Annotation of ExtFile/ExtFile.py, revision 1.1.1.1

1.1       dwinter     1: """ ExtFile product module """
                      2: # -*- coding: latin-1 -*-
                      3: ###############################################################################
                      4: #
                      5: # Copyright (c) 2001 Gregor Heine <mac.gregor@gmx.de>. All rights reserved.
                      6: # ExtFile Home: http://www.zope.org/Members/MacGregor/ExtFile/index_html
                      7: #
                      8: # Redistribution and use in source and binary forms, with or without
                      9: # modification, are permitted provided that the following conditions
                     10: # are met:
                     11: #
                     12: # 1. Redistributions of source code must retain the above copyright
                     13: #    notice, this list of conditions and the following disclaimer.
                     14: # 2. Redistributions in binary form must reproduce the above copyright
                     15: #    notice, this list of conditions and the following disclaimer in the
                     16: #    documentation and/or other materials provided with the distribution.
                     17: # 3. The name of the author may not be used to endorse or promote products
                     18: #    derived from this software without specific prior written permission
                     19: #
                     20: # Disclaimer
                     21: #
                     22: # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
                     23: # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
                     24: # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
                     25: # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
                     26: # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
                     27: # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
                     28: # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
                     29: # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
                     30: # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
                     31: # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
                     32: #   
                     33: #  In accordance with the license provided for by the software upon
                     34: #  which some of the source code has been derived or used, the following
                     35: #  acknowledgement is hereby provided :
                     36: #
                     37: #      "This product includes software developed by Digital Creations
                     38: #      for use in the Z Object Publishing Environment
                     39: #      (http://www.zope.org/)."
                     40: #
                     41: ###############################################################################
                     42: 
                     43: __doc__ = """ExtFile product module.
                     44:     The ExtFile-Product works like the Zope File-product, but stores 
                     45:     the uploaded file externally in a repository-direcory."""
                     46: 
                     47: __version__='1.5.4'
                     48: 
                     49: from Products.ZCatalog.CatalogPathAwareness import CatalogAware
                     50: from OFS.SimpleItem import SimpleItem
                     51: from OFS.PropertyManager import PropertyManager
                     52: from OFS.Cache import Cacheable
                     53: from Globals import HTMLFile, MessageDialog, InitializeClass, package_home
                     54: from AccessControl import ClassSecurityInfo, getSecurityManager
                     55: from AccessControl import Permissions
                     56: from Acquisition import aq_acquire
                     57: from mimetypes import guess_extension
                     58: from webdav.Lockable import ResourceLockedError
                     59: from webdav.common import rfc1123_date
                     60: from DateTime import DateTime
                     61: import urllib, os, string, types, sha, base64
                     62: from os.path import join, isfile
                     63: from tempfile import TemporaryFile
                     64: from Products.ExtFile import TM
                     65: 
                     66: from webdav.WriteLockInterface import WriteLockInterface
                     67: from IExtFile import IExtFile
                     68: 
                     69: #from zLOG import *
                     70: 
                     71: 
                     72: _SUBSYS = 'ExtFile'
                     73: _debug = 0
                     74: 
                     75: #ersetzt zLOG -- Aenderung DW 24.1.2007
                     76: 
                     77: from logging import *
                     78: 
                     79: def LOG(txt,method,txt2):
                     80:     """logging"""
                     81:     info(txt+ txt2)
                     82: 
                     83: 
                     84: try: import Zope2
                     85: except ImportError: ZOPE28 = 0
                     86: else: ZOPE28 = 1
                     87: 
                     88: try: from zope.contenttype import guess_content_type
                     89: except ImportError:
                     90:     try: from zope.app.content_types import guess_content_type
                     91:     except ImportError: from OFS.content_types import guess_content_type
                     92: 
                     93: try: from zExceptions import Redirect
                     94: except ImportError: Redirect = 'Redirect'
                     95: 
                     96: try: from ZPublisher.Iterators import IStreamIterator
                     97: except ImportError: IStreamIterator = None
                     98: 
                     99: ViewPermission = Permissions.view
                    100: AccessPermission = Permissions.view_management_screens
                    101: ChangePermission = 'Change ExtFile/ExtImage'
                    102: DownloadPermission = 'Download ExtFile/ExtImage'
                    103: 
                    104: import re
                    105: copy_of_re = re.compile('(^(copy[0-9]*_of_)+)')
                    106: 
                    107: from Config import *
                    108: 
                    109: manage_addExtFileForm = HTMLFile('dtml/extFileAdd', globals()) 
                    110: 
                    111: 
                    112: def manage_addExtFile(self, id='', title='', descr='', file='', 
                    113:                       content_type='', permission_check=0, redirect_default_view=0, REQUEST=None):
                    114:     """ Add an ExtFile to a folder. """
                    115:     if not id and getattr(file, 'filename', None) is not None:
                    116:         # generate id from filename and make sure, it has no 'bad' chars
                    117:         id = file.filename
                    118:         id = id[max(string.rfind(id,'/'), 
                    119:                     string.rfind(id,'\\'), 
                    120:                     string.rfind(id,':'))+1:]
                    121:         title = title or id
                    122:         id = normalize_id(id)
                    123:     tempExtFile = ExtFile(id, title, descr, permission_check, redirect_default_view)
                    124:     self._setObject(id, tempExtFile)
                    125:     if file != '':
                    126:         self._getOb(id).manage_file_upload(file, content_type)
                    127:     if REQUEST is not None:
                    128:         return self.manage_main(self, REQUEST, update_menu=0)
                    129:     return id
                    130: 
                    131: 
                    132: 
                    133: class ExtFile(CatalogAware, SimpleItem, PropertyManager, Cacheable):
                    134:     """ The ExtFile-Product works like the Zope File-product, but stores 
                    135:         the uploaded file externally in a repository-direcory. """
                    136: 
                    137:     __implements__ = (IExtFile, WriteLockInterface)
                    138:     
                    139:     # what properties have we?
                    140:     _properties = (
                    141:         {'id':'title',                          'type':'string',    'mode': 'w'},
                    142:         {'id':'descr',                          'type':'text',      'mode': 'w'},
                    143:         {'id':'content_type',                   'type':'string',    'mode': 'w'},
                    144:         {'id':'use_download_permission_check',  'type':'boolean',   'mode': 'w'},
                    145:         {'id':'redirect_default_view',          'type':'boolean',   'mode': 'w'},
                    146:     )
                    147:     use_download_permission_check = 0
                    148:     redirect_default_view = 0
                    149:     
                    150:     # what management options are there?
                    151:     manage_options = ((
                    152:         {'label':'Edit',            'action': 'manage_main'             },
                    153:         {'label':'View',            'action': ''                        },
                    154:         {'label':'Upload',          'action': 'manage_uploadForm'       },) +
                    155:         PropertyManager.manage_options +
                    156:         SimpleItem.manage_options[1:] +
                    157:         Cacheable.manage_options
                    158:     ) 
                    159:     
                    160:     security = ClassSecurityInfo()
                    161: 
                    162:     # what do people think they're adding? 
                    163:     meta_type = 'ExtFile'
                    164:     
                    165:     # location of the file-repository
                    166:     _repository = REPOSITORY_PATH
                    167:     
                    168:     # make sure the download permission is available
                    169:     security.setPermissionDefault(DownloadPermission, ('Manager',))
                    170:     
                    171:     # the above does not work in Zope < 2.8
                    172:     if not ZOPE28:
                    173:         security.declareProtected(DownloadPermission, '_dummy')
                    174: 
                    175:     # MIME-Type Dictionary. To add a MIME-Type, add a file in the directory 
                    176:     # icons/_category_/_subcategory-icon-file_
                    177:     # example: Icon tifficon.gif for the MIME-Type image/tiff goes to 
                    178:     # icons/image/tifficon.gif and the dictionary must be updated like this: 
                    179:     # 'image':{'tiff':'tifficon.gif','default':'default.gif'}, ...
                    180:     _types={'image':                    
                    181:                 {'default':'default.gif'},
                    182:             'text':
                    183:                 {'html':'html.gif', 'xml':'xml.gif', 'default':'default.gif', 
                    184:                  'python':'py.gif'},
                    185:             'application':
                    186:                 {'pdf':'pdf.gif', 'zip':'zip.gif', 'tar':'zip.gif', 
                    187:                  'msword':'doc.gif', 'excel':'xls.gif', 'powerpoint':'ppt.gif', 
                    188:                  'default':'default.gif'},
                    189:             'video':
                    190:                 {'default':'default.gif'},
                    191:             'audio':
                    192:                 {'default':'default.gif'},
                    193:             'default':'default.gif'
                    194:         }
                    195: 
                    196:     ################################
                    197:     # Init method                  #
                    198:     ################################
                    199:     
                    200:     def __init__(self, id, title='', descr='', permission_check=0, redirect_default_view=0): 
                    201:         """ Initialize a new instance of ExtFile """
                    202:         self.id = id
                    203:         self.title = title
                    204:         self.descr = descr
                    205:         self.use_download_permission_check = permission_check
                    206:         self.redirect_default_view = redirect_default_view
                    207:         self.__version__ = __version__
                    208:         self.filename = []
                    209:         self.content_type = ''
                    210:             
                    211:     ################################
                    212:     # Public methods               #
                    213:     ################################
                    214:     
                    215:     def __str__(self):
                    216:         return self.index_html()
                    217:     
                    218:     def __len__(self):
                    219:         return 1
                    220:     
                    221:     def _if_modified_since_request_handler(self, REQUEST):
                    222:         """ HTTP If-Modified-Since header handling: return True if
                    223:             we can handle this request by returning a 304 response.
                    224:         """
                    225:         header = REQUEST.get_header('If-Modified-Since', None)
                    226:         if header is not None:
                    227:             header = string.split(header, ';')[0]
                    228:             try:    mod_since = long(DateTime(header).timeTime())
                    229:             except: mod_since = None
                    230:             if mod_since is not None:
                    231:                 if self._p_mtime:
                    232:                     last_mod = long(self._p_mtime)
                    233:                 else:
                    234:                     last_mod = long(0)
                    235:                 if last_mod > 0 and last_mod < mod_since:
                    236:                     # Set headers for Apache caching
                    237:                     last_mod = rfc1123_date(self._p_mtime)
                    238:                     REQUEST.RESPONSE.setHeader('Last-Modified', last_mod)
                    239:                     REQUEST.RESPONSE.setHeader('Content-Type', self.content_type)
                    240:                     # RFC violation. See http://collector.zope.org/Zope/544
                    241:                     #REQUEST.RESPONSE.setHeader('Content-Length', self.get_size())
                    242:                     REQUEST.RESPONSE.setStatus(304)
                    243:                     return 1
                    244: 
                    245:     def _redirect_default_view_request_handler(self, icon, preview, REQUEST):
                    246:         """ redirect_default_view property handling: return True if
                    247:             we can handle this request by returning a 302 response.
                    248:             Patch provided by Oliver Bleutgen.
                    249:         """
                    250:         if self.redirect_default_view:
                    251:             if self.static_mode() and not icon:
                    252:                 static_url = self._static_url(preview=preview)
                    253:                 if static_url != self.absolute_url():
                    254:                     REQUEST.RESPONSE.redirect(static_url)
                    255:                     return 1
                    256: 
                    257:     security.declareProtected(ViewPermission, 'index_html')
                    258:     def index_html (self, icon=0, preview=0, width=None, height=None, 
                    259:                     REQUEST=None):
                    260:         """ Return the file with it's corresponding MIME-type """
                    261: 
                    262:         if REQUEST is not None:
                    263:             if self._if_modified_since_request_handler(REQUEST):
                    264:                 self.ZCacheable_set(None)
                    265:                 return ''
                    266: 
                    267:             if self._redirect_default_view_request_handler(icon, preview, REQUEST):
                    268:                 return ''
                    269: 
                    270:         filename, content_type, icon, preview = self._get_file_to_serve(icon, preview)
                    271:         filename = self._get_fsname(filename)
                    272: 
                    273:         if _debug > 1: LOG(_SUBSYS, INFO, 'serving %s, %s, %s, %s' %(filename, content_type, icon, preview))
                    274: 
                    275:         cant_read_exc = "Can't read: "
                    276:         if filename:
                    277:             try: size = os.stat(filename)[6]
                    278:             except: raise cant_read_exc, ("%s (%s)" %(self.id, filename))
                    279:         else:
                    280:             filename = join(package_home(globals()), 'icons', 'broken.gif')
                    281:             try: size = os.stat(filename)[6]
                    282:             except: raise cant_read_exc, ("%s (%s)" %(self.id, filename))
                    283:             content_type = 'image/gif'
                    284:             icon = 1
                    285: 
                    286:         if icon==0 and width is not None and height is not None:
                    287:             data = TemporaryFile() # hold resized image
                    288:             try:
                    289:                 from PIL import Image
                    290:                 im = Image.open(filename) 
                    291:                 if im.mode!='RGB':
                    292:                     im = im.convert('RGB')
                    293:                 filter = Image.BICUBIC
                    294:                 if hasattr(Image, 'ANTIALIAS'): # PIL 1.1.3
                    295:                     filter = Image.ANTIALIAS
                    296:                 im = im.resize((int(width),int(height)), filter)
                    297:                 im.save(data, 'JPEG', quality=85)
                    298:             except:
                    299:                 data = open(filename, 'rb')
                    300:             else:
                    301:                 data.seek(0,2)
                    302:                 size = data.tell()
                    303:                 data.seek(0)
                    304:                 content_type = 'image/jpeg'
                    305:         else:
                    306:             data = open(filename, 'rb')
                    307: 
                    308:         close_data = 1
                    309:         try:
                    310:             if REQUEST is not None:
                    311:                 last_mod = rfc1123_date(self._p_mtime)
                    312:                 REQUEST.RESPONSE.setHeader('Last-Modified', last_mod)
                    313:                 REQUEST.RESPONSE.setHeader('Content-Type', content_type)
                    314:                 REQUEST.RESPONSE.setHeader('Content-Length', size)
                    315:                 self.ZCacheable_set(None)
                    316: 
                    317:                 # Support Zope 2.7.1 IStreamIterator
                    318:                 if IStreamIterator is not None:
                    319:                     close_data = 0
                    320:                     return stream_iterator(data)
                    321: 
                    322:                 blocksize = 2<<16
                    323:                 while 1:
                    324:                     buffer = data.read(blocksize)
                    325:                     REQUEST.RESPONSE.write(buffer)
                    326:                     if len(buffer) < blocksize:
                    327:                         break
                    328:                 return ''
                    329:             else:
                    330:                 return data.read()
                    331:         finally:
                    332:             if close_data: data.close()
                    333:     
                    334:     security.declareProtected(ViewPermission, 'view_image_or_file')
                    335:     def view_image_or_file(self):
                    336:         """ The default view of the contents of the File or Image. """
                    337:         raise Redirect, self.absolute_url()
                    338: 
                    339:     security.declareProtected(ViewPermission, 'link')
                    340:     def link(self, text='', **args):
                    341:         """ Return a HTML link tag to the file """
                    342:         if text=='': text = self.title_or_id()
                    343:         strg = '<a href="%s"' % (self._static_url())
                    344:         for key in args.keys():
                    345:             value = args.get(key)
                    346:             strg = '%s %s="%s"' % (strg, key, value)
                    347:         strg = '%s>%s</a>' % (strg, text)
                    348:         return strg
                    349:     
                    350:     security.declareProtected(ViewPermission, 'icon_gif')
                    351:     def icon_gif(self):
                    352:         """ Return an icon for the file's MIME-Type """
                    353:         raise Redirect, self._static_url(icon=1)
                    354:     
                    355:     security.declareProtected(ViewPermission, 'icon_tag')
                    356:     def icon_tag(self):
                    357:         """ Generate the HTML IMG tag for the icon """
                    358:         return '<img src="%s" border="0" />' % self._static_url(icon=1)
                    359:     
                    360:     security.declareProtected(ViewPermission, 'icon_html')
                    361:     def icon_html(self):
                    362:         """ Same as icon_tag """
                    363:         return self.icon_tag()
                    364:     
                    365:     security.declareProtected(ViewPermission, 'is_broken')
                    366:     def is_broken(self):
                    367:         """ Check if external file exists and return true (1) or false (0) """
                    368:         return not self._get_fsname(self.filename)
                    369:     
                    370:     security.declareProtected(ViewPermission, 'get_size')
                    371:     def get_size(self):
                    372:         """ Returns the size of the file or image """
                    373:         fn = self._get_fsname(self.filename)
                    374:         if fn:
                    375:             return os.stat(fn)[6]
                    376:         return 0
                    377:     
                    378:     security.declareProtected(ViewPermission, 'rawsize')
                    379:     def rawsize(self):
                    380:         """ Same as get_size """
                    381:         return self.get_size()
                    382: 
                    383:     security.declareProtected(ViewPermission, 'getSize')
                    384:     def getSize(self):
                    385:         """ Same as get_size """
                    386:         return self.get_size()
                    387: 
                    388:     security.declareProtected(ViewPermission, 'size')
                    389:     def size(self):
                    390:         """ Returns a formatted stringified version of the file size """
                    391:         return self._bytetostring(self.get_size())
                    392:     
                    393:     security.declareProtected(ViewPermission, 'getContentType')
                    394:     def getContentType(self):
                    395:         """ Returns the content type (MIME type) of a file or image. """
                    396:         return self.content_type
                    397:         
                    398:     security.declareProtected(ViewPermission, 'getIconPath')
                    399:     def getIconPath(self):
                    400:         """ Depending on the MIME Type of the file/image an icon
                    401:             can be displayed. This function determines which
                    402:             image in the lib/python/Products/ExtFile/icons/...
                    403:             directory shold be used as icon for this file/image
                    404:         """
                    405:         try:
                    406:             cat, sub = string.split(self.content_type, '/')
                    407:         except ValueError:
                    408:             if getattr(self, 'has_preview', None) is not None:
                    409:                 cat, sub = 'image', ''
                    410:             else:
                    411:                 cat, sub = '', ''
                    412:         if self._types.has_key(cat):
                    413:             file = self._types[cat]['default']
                    414:             for item in self._types[cat].keys():
                    415:                 if string.find(sub, item) >= 0:
                    416:                     file = self._types[cat][item]
                    417:                     break
                    418:             return join('icons', cat, file)
                    419:         return join('icons', self._types['default'])
                    420:     
                    421:     security.declareProtected(ViewPermission, 'static_url')
                    422:     def static_url(self, icon=0, preview=0):
                    423:         """ Returns the static url of the file """
                    424:         return self._static_url(icon, preview)
                    425:                     
                    426:     security.declareProtected(ViewPermission, 'static_mode')
                    427:     def static_mode(self):
                    428:         """ Returns true if serving static urls """
                    429:         return os.environ.get('EXTFILE_STATIC_PATH') is not None
                    430: 
                    431:     security.declareProtected(AccessPermission, 'get_filename')
                    432:     def get_filename(self):
                    433:         """ Returns the filename as file system path. 
                    434:             Used by the ZMI to display the filename.
                    435:         """
                    436:         return self._fsname(self.filename)
                    437: 
                    438:     security.declareProtected(ViewPermission, 'PrincipiaSearchSource')
                    439:     def PrincipiaSearchSource(self):
                    440:         """ Allow file objects to be searched.
                    441:         """
                    442:         if self.content_type.startswith('text/'):
                    443:             return str(self)
                    444:         return ''
                    445: 
                    446:     ################################
                    447:     # Protected management methods #
                    448:     ################################
                    449:     
                    450:     # Management Interface
                    451:     security.declareProtected(AccessPermission, 'manage_main')
                    452:     manage_main = HTMLFile('dtml/extFileEdit', globals())    
                    453:     
                    454:     security.declareProtected(ChangePermission, 'manage_editExtFile')
                    455:     def manage_editExtFile(self, title='', descr='', REQUEST=None): 
                    456:         """ Manage the edited values """
                    457:         if self.title!=title: self.title = title
                    458:         if self.descr!=descr: self.descr = descr
                    459:         # update ZCatalog
                    460:         self.reindex_object()
                    461: 
                    462:         self.ZCacheable_invalidate()
                    463: 
                    464:         if REQUEST is not None:
                    465:             return self.manage_main(self, REQUEST, manage_tabs_message='Saved changes.')                
                    466:     
                    467:     # File upload Interface
                    468:     security.declareProtected(AccessPermission, 'manage_uploadForm')
                    469:     manage_uploadForm = HTMLFile('dtml/extFileUpload', globals())
                    470:     
                    471:     security.declareProtected(ChangePermission, 'manage_upload')
                    472:     def manage_upload(self, file='', content_type='', REQUEST=None):
                    473:         """ Upload file from file handle or string buffer """
                    474:         if self.wl_isLocked():
                    475:             raise ResourceLockedError, "File is locked via WebDAV"
                    476:                             
                    477:         if type(file) == types.StringType:
                    478:             temp_file = TemporaryFile()
                    479:             temp_file.write(file)
                    480:             temp_file.seek(0)
                    481:         else:
                    482:             temp_file = file
                    483:         return self.manage_file_upload(temp_file, content_type, REQUEST)
                    484: 
                    485:     security.declareProtected(ChangePermission, 'manage_file_upload')
                    486:     def manage_file_upload(self, file='', content_type='', REQUEST=None):
                    487:         """ Upload file from file handle or local directory """
                    488:         if self.wl_isLocked():
                    489:             raise ResourceLockedError, "File is locked via WebDAV"
                    490:                             
                    491:         if type(file) == types.StringType:
                    492:             cant_read_exc = "Can't open: "
                    493:             try: file = open(file, 'rb')
                    494:             except: raise cant_read_exc, file
                    495:         if content_type:
                    496:             file = HTTPUpload(file, content_type)
                    497:         self.content_type = self._get_content_type(file, file.read(100), 
                    498:                             self.id, self.content_type)
                    499:         file.seek(0)
                    500:         self._register()    # Register with TM
                    501:         try:
                    502:             new_fn = self._get_ufn(self.filename)
                    503:             self._update_data(file, self._temp_fsname(new_fn))
                    504:         finally:
                    505:             self._dir__unlock()
                    506:         self.filename = new_fn
                    507:         self._afterUpdate()
                    508:         if REQUEST is not None:
                    509:             return self.manage_main(self, REQUEST, manage_tabs_message='Upload complete.')                
                    510: 
                    511:     security.declareProtected(ChangePermission, 'manage_http_upload')
                    512:     def manage_http_upload(self, url, REQUEST=None):
                    513:         """ Upload file from http-server """
                    514:         if self.wl_isLocked():
                    515:             raise ResourceLockedError, "File is locked via WebDAV"
                    516:                             
                    517:         url = urllib.quote(url,'/:')
                    518:         cant_read_exc = "Can't open: "
                    519:         try: file = urllib.urlopen(url)
                    520:         except: raise cant_read_exc, url
                    521:         file = HTTPUpload(file)
                    522:         self.content_type = self._get_content_type(file, file.read(100),
                    523:                             self.id, self.content_type)
                    524:         file.seek(0)
                    525:         self._register()    # Register with TM
                    526:         try:
                    527:             new_fn = self._get_ufn(self.filename)
                    528:             self._update_data(file, self._temp_fsname(new_fn))
                    529:         finally:
                    530:             self._dir__unlock()
                    531:         self.filename = new_fn
                    532:         self._afterUpdate()
                    533:         if REQUEST is not None:
                    534:             return self.manage_main(self, REQUEST, manage_tabs_message='Upload complete.')                
                    535:     
                    536:     security.declareProtected(ChangePermission, 'PUT')
                    537:     def PUT(self, REQUEST, RESPONSE):
                    538:         """ Handle HTTP PUT requests """
                    539:         self.dav__init(REQUEST, RESPONSE)
                    540:         self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
                    541:         file = REQUEST['BODYFILE']
                    542:         content_type = REQUEST.get_header('content-type', None)
                    543:         if content_type:
                    544:             file = HTTPUpload(file, content_type)
                    545:         self.content_type = self._get_content_type(file, file.read(100),
                    546:                             self.id, self.content_type)
                    547:         file.seek(0)
                    548:         self._register()    # Register with TM
                    549:         try:
                    550:             # Need to pass in the path as webdav.NullResource calls PUT
                    551:             # on an unwrapped object.
                    552:             try:
                    553:                 self.aq_parent # This raises AttributeError if no context
                    554:             except AttributeError:
                    555:                 path = self._get_zodb_path(REQUEST.PARENTS[0])
                    556:             else:
                    557:                 path = None
                    558:             new_fn = self._get_ufn(self.filename, path=path)
                    559:             self._update_data(file, self._temp_fsname(new_fn))
                    560:         finally:
                    561:             self._dir__unlock()
                    562:         self.filename = new_fn
                    563:         self._afterUpdate()
                    564:         RESPONSE.setStatus(204)
                    565:         return RESPONSE
                    566:     
                    567:     security.declareProtected('FTP access', 'manage_FTPstat')
                    568:     security.declareProtected('FTP access', 'manage_FTPlist')
                    569:     security.declareProtected('FTP access', 'manage_FTPget')
                    570:     def manage_FTPget(self):
                    571:         """ Return body for FTP """
                    572:         return self.index_html(REQUEST=self.REQUEST)
                    573:     
                    574:     ################################
                    575:     # Private methods              #
                    576:     ################################
                    577: 
                    578:     def _access_permitted(self, REQUEST=None):
                    579:         """ Check if the user is allowed to download the file """
                    580:         if REQUEST is None and getattr(self, 'REQUEST', None) is not None:
                    581:             REQUEST = self.REQUEST
                    582:         if getattr(self, 'use_download_permission_check', 0) and \
                    583:            (REQUEST is None or 
                    584:             not getSecurityManager().getUser().has_permission(
                    585:                                         DownloadPermission, self)
                    586:            ):
                    587:             return 0
                    588:         else: 
                    589:             return 1
                    590:     
                    591:     def _get_content_type(self, file, body, id, content_type=None):
                    592:         """ Determine the mime-type """
                    593:         headers = getattr(file, 'headers', None)
                    594:         if headers and headers.has_key('content-type'):
                    595:             content_type = headers['content-type']
                    596:         else:
                    597:             if type(body) is not type(''): body = body.data
                    598:             content_type, enc = guess_content_type(getattr(file,'filename',id),
                    599:                                                    body, content_type)
                    600:         cutoff = content_type.find(';')
                    601:         if cutoff >= 0:
                    602:             return content_type[:cutoff]
                    603:         return content_type
                    604:     
                    605:     def _update_data(self, infile, outfile):
                    606:         """ Store infile to outfile """
                    607:         if type(infile) == types.ListType:
                    608:             infile = self._fsname(infile)
                    609:         if type(outfile) == types.ListType:
                    610:             outfile = self._fsname(outfile)
                    611:         try:
                    612:             self._copy(infile, outfile)
                    613:         except:
                    614:             if isfile(outfile): # This is always a .tmp file
                    615:                 try: os.remove(outfile)
                    616:                 except OSError: pass
                    617:             raise
                    618:         else:
                    619:             self.http__refreshEtag()
                    620:     
                    621:     def _copy(self, infile, outfile):
                    622:         """ Read binary data from infile and write it to outfile
                    623:             infile and outfile may be strings, in which case a file with that
                    624:             name is opened, or filehandles, in which case they are accessed
                    625:             directly.
                    626:         """
                    627:         if type(infile) is types.StringType: 
                    628:             try:
                    629:                 instream = open(infile, 'rb')
                    630:             except IOError:
                    631:                 raise IOError, ("%s (%s)" %(self.id, infile))
                    632:             close_in = 1
                    633:         else:
                    634:             instream = infile
                    635:             close_in = 0
                    636:         if type(outfile) is types.StringType: 
                    637:             umask = os.umask(REPOSITORY_UMASK)
                    638:             try:
                    639:                 outstream = open(outfile, 'wb')
                    640:                 os.umask(umask)
                    641:                 self._dir__unlock()   # unlock early
                    642:             except IOError:
                    643:                 os.umask(umask)
                    644:                 raise IOError, ("%s (%s)" %(self.id, outfile))
                    645:             close_out = 1
                    646:         else:
                    647:             outstream = outfile
                    648:             close_out = 0
                    649:         try:
                    650:             blocksize = 2<<16
                    651:             block = instream.read(blocksize)
                    652:             outstream.write(block)
                    653:             while len(block)==blocksize:
                    654:                 block = instream.read(blocksize)
                    655:                 outstream.write(block)
                    656:         except IOError:
                    657:             raise IOError, ("%s (%s)" %(self.id, filename))
                    658:         try: instream.seek(0)
                    659:         except: pass
                    660:         if close_in: instream.close()
                    661:         if close_out: outstream.close()
                    662:     
                    663:     def _undo(self):
                    664:         """ Restore filename after delete or copy-paste """
                    665:         fn = self._fsname(self.filename)
                    666:         if not isfile(fn) and isfile(fn+'.undo'): 
                    667:             self._register()    # Register with TM
                    668:             os.rename(fn+'.undo', self._temp_fsname(self.filename))
                    669:     
                    670:     def _fsname(self, filename):
                    671:         """ Generates the full filesystem name, incuding directories from 
                    672:             self._repository and filename
                    673:         """
                    674:         path = [INSTANCE_HOME]
                    675:         path.extend(self._repository)
                    676:         if type(filename) == types.ListType:
                    677:             path.extend(filename)
                    678:         elif filename != '':
                    679:             path.append(filename)
                    680:         return apply(join, path)
                    681: 
                    682:     def _temp_fsname(self, filename):
                    683:         """ Generates the full filesystem name of the temporary file """
                    684:         return '%s.tmp' % self._fsname(filename)
                    685: 
                    686:     def _get_fsname(self, filename):
                    687:         """ Returns the full filesystem name, preferring tmp over main. 
                    688:             Also attempts to undo. Returns None if the file is broken.
                    689:         """
                    690:         tmp_fn = self._temp_fsname(filename)
                    691:         if isfile(tmp_fn):
                    692:             return tmp_fn
                    693:         fn = self._fsname(filename)
                    694:         if isfile(fn):
                    695:             return fn
                    696:         self._undo()
                    697:         if isfile(tmp_fn):
                    698:             return tmp_fn
                    699: 
                    700:     # b/w compatibility
                    701:     def _get_filename(self, filename):
                    702:         """ Deprecated, use _get_fsname """
                    703:         return self._get_fsname(filename)
                    704: 
                    705:     def _get_ufn(self, filename, path=None, content_type=None, lock=1):
                    706:         """ If no unique filename has been generated, generate one
                    707:             otherwise, return the existing one.
                    708:         """
                    709:         if UNDO_POLICY==ALWAYS_BACKUP or filename==[]: 
                    710:             new_fn = self._get_new_ufn(path=path, content_type=content_type, lock=lock)
                    711:         else: 
                    712:             new_fn = filename[:]
                    713:         if filename:
                    714:             old_fn = self._fsname(filename)
                    715:             if UNDO_POLICY==ALWAYS_BACKUP: 
                    716:                 try: os.rename(old_fn, old_fn+'.undo')
                    717:                 except OSError: pass
                    718:             else:
                    719:                 try: os.rename(old_fn+'.undo', old_fn)
                    720:                 except OSError: pass
                    721:         return new_fn
                    722: 
                    723:     def _get_new_ufn(self, path=None, content_type=None, lock=1):
                    724:         """ Create a new unique filename """
                    725:         id = self.id
                    726: 
                    727:         # hack so the files are not named copy_of_foo
                    728:         if COPY_OF_PROTECTION:
                    729:             match = copy_of_re.match(id)
                    730:             if match is not None:
                    731:                 id = id[len(match.group(1)):]
                    732: 
                    733:         # get name and extension components from id
                    734:         pos = string.rfind(id, '.')
                    735:         if (pos+1):
                    736:             id_name = id[:pos]
                    737:             id_ext = id[pos:]
                    738:         else:
                    739:             id_name = id
                    740:             id_ext = ''
                    741: 
                    742:         if not content_type:
                    743:             content_type = self.content_type
                    744: 
                    745:         if REPOSITORY_EXTENSIONS in (MIMETYPE_APPEND, MIMETYPE_REPLACE):
                    746:             mime_ext = guess_extension(content_type)
                    747:             if mime_ext is not None:
                    748:                 if mime_ext in ('.jpeg', '.jpe'): 
                    749:                     mime_ext = '.jpg'   # for IE/Win :-(
                    750:                 if mime_ext in ('.obj',):
                    751:                     mime_ext = '.exe'   # b/w compatibility
                    752:                 if mime_ext in ('.tiff',):
                    753:                     mime_ext = '.tif'   # b/w compatibility
                    754:                 # don't change extensions of unknown binaries
                    755:                 if not (content_type == 'application/octet-stream' and id_ext):
                    756:                     if REPOSITORY_EXTENSIONS == MIMETYPE_APPEND:
                    757:                         id_name = id_name + id_ext
                    758:                     id_ext = mime_ext
                    759:         
                    760:         # generate directory structure
                    761:         if path is not None:
                    762:             rel_url_list = path
                    763:         else:
                    764:             rel_url_list = self._get_zodb_path()
                    765: 
                    766:         dirs = []
                    767:         if REPOSITORY == SYNC_ZODB: 
                    768:             dirs = rel_url_list
                    769:         elif REPOSITORY in (SLICED, SLICED_REVERSE, SLICED_HASH):
                    770:             if REPOSITORY == SLICED_HASH:
                    771:                 # increase distribution by including the path in the hash
                    772:                 hashed = ''.join(list(rel_url_list)+[id_name])
                    773:                 temp = base64.encodestring(sha.new(hashed).digest())[:-1]
                    774:                 temp = temp.replace('/', '_')
                    775:                 temp = temp.replace('+', '_')
                    776:             elif REPOSITORY == SLICED_REVERSE: 
                    777:                 temp = list(id_name)
                    778:                 temp.reverse()
                    779:                 temp = ''.join(temp)
                    780:             else:
                    781:                 temp = id_name
                    782:             for i in range(SLICE_DEPTH):
                    783:                 if len(temp)<SLICE_WIDTH*(SLICE_DEPTH-i): 
                    784:                     dirs.append(SLICE_WIDTH*'_')
                    785:                 else:
                    786:                     dirs.append(temp[:SLICE_WIDTH])
                    787:                     temp=temp[SLICE_WIDTH:]
                    788:         elif REPOSITORY == CUSTOM:
                    789:             method = aq_acquire(self, CUSTOM_METHOD)
                    790:             dirs = method(rel_url_list, id)
                    791: 
                    792:         if NORMALIZE_CASE == NORMALIZE:
                    793:             dirs = [d.lower() for d in dirs]
                    794:         
                    795:         # make directories
                    796:         dirpath = self._fsname(dirs)
                    797:         if not os.path.isdir(dirpath):
                    798:             mkdir_exc = "Can't create directory: "
                    799:             umask = os.umask(REPOSITORY_UMASK)
                    800:             try:
                    801:                 os.makedirs(dirpath)
                    802:                 os.umask(umask)
                    803:             except:
                    804:                 os.umask(umask)
                    805:                 raise mkdir_exc, dirpath
                    806: 
                    807:         # generate file name
                    808:         fileformat = FILE_FORMAT
                    809:         # time/counter (%t)
                    810:         if string.find(fileformat, "%t")>=0:
                    811:             fileformat = string.replace(fileformat, "%t", "%c")
                    812:             counter = int(DateTime().strftime('%m%d%H%M%S'))
                    813:         else:
                    814:             counter = 0
                    815:         invalid_format_exc = "Invalid file format: "
                    816:         if string.find(fileformat, "%c")==-1:
                    817:             raise invalid_format_exc, FILE_FORMAT
                    818:         # user (%u)
                    819:         if string.find(fileformat, "%u")>=0:
                    820:             if (getattr(self, 'REQUEST', None) is not None and
                    821:                self.REQUEST.has_key('AUTHENTICATED_USER')):
                    822:                 user = getSecurityManager().getUser().getUserName()
                    823:                 fileformat = string.replace(fileformat, "%u", user)
                    824:             else:
                    825:                 fileformat = string.replace(fileformat, "%u", "")
                    826:         # path (%p)
                    827:         if string.find(fileformat, "%p")>=0:
                    828:             temp = string.joinfields(rel_url_list, "_")
                    829:             fileformat = string.replace(fileformat, "%p", temp)
                    830:         # file and extension (%n and %e)
                    831:         if string.find(fileformat,"%n")>=0 or string.find(fileformat,"%e")>=0:
                    832:             fileformat = string.replace(fileformat, "%n", id_name)
                    833:             fileformat = string.replace(fileformat, "%e", id_ext)
                    834: 
                    835:         # lock the directory
                    836:         if lock: self._dir__lock(dirpath)
                    837: 
                    838:         # search for unique filename
                    839:         if counter: 
                    840:             fn = join(dirpath, string.replace(fileformat, "%c", ".%s" % counter))
                    841:         else: 
                    842:             fn = join(dirpath, string.replace(fileformat, "%c", ''))
                    843:         while isfile(fn) or isfile(fn+'.undo') or isfile(fn+'.tmp'):
                    844:             counter = counter + 1
                    845:             fn = join(dirpath, string.replace(fileformat, "%c", ".%s" % counter))
                    846:         if counter: 
                    847:             fileformat = string.replace(fileformat, "%c", ".%s" % counter)
                    848:         else: 
                    849:             fileformat = string.replace(fileformat, "%c", '')
                    850: 
                    851:         dirs.append(fileformat)
                    852:         return dirs
                    853:         
                    854:     def _static_url(self, icon=0, preview=0):
                    855:         """ Return the static url of the file """
                    856:         static_path = os.environ.get('EXTFILE_STATIC_PATH')
                    857:         if static_path is not None:
                    858:             filename, content_type, icon, preview = \
                    859:                         self._get_file_to_serve(icon, preview)
                    860:             if icon:
                    861:                 # cannot serve statically
                    862:                 return '%s?icon=1' % self.absolute_url()
                    863:             else:
                    864:                 # rewrite to static url
                    865:                 static_host = os.environ.get('EXTFILE_STATIC_HOST')
                    866:                 host = self.REQUEST.SERVER_URL
                    867:                 if static_host is not None:
                    868:                     if host[:8] == 'https://':
                    869:                         host = 'https://' + static_host
                    870:                     else:
                    871:                         host = 'http://' + static_host
                    872:                 host = host + urllib.quote(static_path) + '/'
                    873:                 return host + urllib.quote('/'.join(filename))
                    874:         else:
                    875:             if icon:
                    876:                 return '%s?icon=1' % self.absolute_url()
                    877:             elif preview:
                    878:                 return '%s?preview=1' % self.absolute_url()
                    879:             else:
                    880:                 return self.absolute_url()
                    881: 
                    882:     def _get_file_to_serve(self, icon=0, preview=0):
                    883:         """ Find out about the file we are going to serve """
                    884:         if not self._access_permitted(): 
                    885:             preview = 1
                    886:         if preview and not getattr(self, 'has_preview', 0): 
                    887:             icon = 1
                    888:         
                    889:         if icon:
                    890:             filename = join(package_home(globals()), self.getIconPath())
                    891:             content_type = 'image/gif'
                    892:         elif preview:
                    893:             filename = self.prev_filename
                    894:             content_type = self.prev_content_type
                    895:         else:
                    896:             filename = self.filename
                    897:             content_type = self.content_type
                    898:         
                    899:         return filename, content_type, icon, preview
                    900: 
                    901:     def _get_zodb_path(self, parent=None):
                    902:         """ Returns the ZODB path of the parent object """
                    903:         # XXX: The Photo product uploads into unwrapped ExtImages.
                    904:         # As we can not reliably guess our parent object we fall back
                    905:         # to the old behavior. This means that Photos will always
                    906:         # use ZODB_PATH = VIRTUAL independent of config settings.
                    907:         try:
                    908:             from Products.Photo.ExtPhotoImage import PhotoImage
                    909:         except ImportError:
                    910:             pass
                    911:         else:
                    912:             if isinstance(self, PhotoImage):
                    913:                 path = self.absolute_url(1).split('/')[:-1]
                    914:                 return filter(None, path)
                    915:         # XXX: End of hack
                    916: 
                    917:         # For normal operation objects must be wrapped
                    918:         if parent is None:
                    919:             parent = self.aq_parent
                    920: 
                    921:         if ZODB_PATH == VIRTUAL:
                    922:             path = parent.absolute_url(1).split('/')
                    923:         else:
                    924:             path = list(parent.getPhysicalPath())
                    925:         return filter(None, path)
                    926: 
                    927:     def _bytetostring(self, value):
                    928:         """ Convert an int-value (file-size in bytes) to an String
                    929:             with the file-size in Byte, KB or MB
                    930:         """
                    931:         bytes = float(value)
                    932:         if bytes>=1000:
                    933:             bytes = bytes/1024
                    934:             if bytes>=1000:
                    935:                 bytes = bytes/1024
                    936:                 typ = ' MB'
                    937:             else:
                    938:                 typ = ' KB'
                    939:         else:
                    940:             typ = ' Bytes'
                    941:         strg = '%4.2f'%bytes
                    942:         strg = strg[:4]
                    943:         if strg[3]=='.': strg = strg[:3]
                    944:         strg = strg+typ
                    945:         return strg
                    946:         
                    947:     def _afterUpdate(self):
                    948:         """ Called whenever the file data has been updated. 
                    949:             Invokes the manage_afterUpdate() hook.
                    950:         """
                    951:         self.ZCacheable_invalidate()
                    952: 
                    953:         return self.manage_afterUpdate(self._get_fsname(self.filename), 
                    954:                                        self.content_type, self.get_size())
                    955:         
                    956:     ################################
                    957:     # Special management methods   #
                    958:     ################################
                    959:     
                    960:     security.declarePrivate('manage_afterClone')
                    961:     def manage_afterClone(self, item, new_fn=None):
                    962:         """ When a copy of the object is created (zope copy-paste-operation),
                    963:             this function is called by CopySupport.py. A copy of the external 
                    964:             file is created and self.filename is changed.
                    965:         """
                    966:         call_afterUpdate = 0
                    967:         try: 
                    968:             self.aq_parent # This raises AttributeError if no context
                    969:         except AttributeError: 
                    970:             self._v_has_been_cloned=1   # This is to make webdav COPY work
                    971:         else:
                    972:             fn = self._get_fsname(self.filename)
                    973:             if fn:
                    974:                 self._register()    # Register with TM
                    975:                 try:
                    976:                     new_fn = new_fn or self._get_new_ufn()
                    977:                     self._update_data(fn, self._temp_fsname(new_fn))
                    978:                     self.filename = new_fn
                    979:                     call_afterUpdate = 1
                    980:                 finally:
                    981:                     self._dir__unlock()
                    982:                 if call_afterUpdate:
                    983:                     self._afterUpdate()
                    984:         return ExtFile.inheritedAttribute("manage_afterClone")(self, item)
                    985:         
                    986:     security.declarePrivate('manage_afterAdd')
                    987:     def manage_afterAdd(self, item, container):
                    988:         """ This method is called, whenever _setObject in ObjectManager gets 
                    989:             called. This is the case after a normal add and if the object is a 
                    990:             result of cut-paste- or rename-operation. In the first case, the
                    991:             external files doesn't exist yet, otherwise it was renamed to .undo
                    992:             by manage_beforeDelete before and must be restored by _undo().
                    993:         """
                    994:         self._undo()
                    995:         if hasattr(self, "_v_has_been_cloned"):
                    996:             delattr(self, "_v_has_been_cloned")
                    997:             self.manage_afterClone(item)
                    998:         return ExtFile.inheritedAttribute("manage_afterAdd")(self, item, container)
                    999:     
                   1000:     security.declarePrivate('manage_beforeDelete')
                   1001:     def manage_beforeDelete(self, item, container):
                   1002:         """ This method is called, when the object is deleted. To support 
                   1003:             undo-functionality and because this happens too, when the object 
                   1004:             is moved (cut-paste) or renamed, the external file is not deleted. 
                   1005:             It is just renamed to filename.undo and remains in the 
                   1006:             repository, until it is deleted manually.
                   1007:         """
                   1008:         tmp_fn = self._temp_fsname(self.filename)
                   1009:         fn = self._fsname(self.filename)
                   1010:         if isfile(tmp_fn):
                   1011:             try: os.rename(tmp_fn, fn+'.undo')
                   1012:             except OSError: pass
                   1013:             else:
                   1014:                 try: os.remove(fn)
                   1015:                 except OSError: pass
                   1016:         elif isfile(fn):
                   1017:             try: os.rename(fn, fn+'.undo')
                   1018:             except OSError: pass
                   1019:         return ExtFile.inheritedAttribute("manage_beforeDelete")(self, item, container)
                   1020: 
                   1021:     security.declarePrivate('manage_afterUpdate')
                   1022:     def manage_afterUpdate(self, filename, content_type, size):
                   1023:         """ This method is called whenever the file data has been updated.
                   1024:             May be overridden by subclasses to perform additional operations.
                   1025:             The 'filename' argument contains the path as returned by get_fsname().
                   1026:         """
                   1027:         pass
                   1028: 
                   1029:     security.declarePrivate('get_fsname')
                   1030:     def get_fsname(self):
                   1031:         """ Returns the current file system path of the file or image. 
                   1032:             This path can be used to access the file even while a 
                   1033:             transaction is in progress (aka Zagy's revenge :-). 
                   1034:             Returns None if the file does not exist in the repository. 
                   1035:         """
                   1036:         return self._get_fsname(self.filename)
                   1037: 
                   1038:     ################################
                   1039:     # Repository locking methods   #
                   1040:     ################################
                   1041: 
                   1042:     def _dir__lock(self, dir):
                   1043:         """ Lock a directory """
                   1044:         if hasattr(self, '_v_dir__lock'):
                   1045:             raise DirLockError, 'Double lock in thread'
                   1046:         self._v_dir__lock = DirLock(dir)
                   1047:         
                   1048:     def _dir__unlock(self):
                   1049:         """ Unlock a previously locked directory """
                   1050:         if hasattr(self, '_v_dir__lock'):
                   1051:             self._v_dir__lock.release()
                   1052:             delattr(self, '_v_dir__lock')
                   1053: 
                   1054:     ################################
                   1055:     # Transaction manager methods  #
                   1056:     ################################
                   1057: 
                   1058:     def _register(self):
                   1059:         if _debug: LOG(_SUBSYS, INFO, 'registering %s' % TM.contains(self))
                   1060:         TM.register(self)
                   1061:         if _debug: LOG(_SUBSYS, INFO, 'registered %s' % TM.contains(self))
                   1062: 
                   1063:     def _begin(self):
                   1064:         self._v_begin_called = 1    # for tests
                   1065:         if _debug: LOG(_SUBSYS, INFO, 'beginning %s' % self.id) 
                   1066: 
                   1067:     def _finish(self):
                   1068:         """ Commits the temporary file """
                   1069:         self._v_finish_called = 1   # for tests
                   1070:         TM.remove(self)             # for tests
                   1071:         if self.filename:
                   1072:             tmp_fn = self._temp_fsname(self.filename)
                   1073:             if _debug: LOG(_SUBSYS, INFO, 'finishing %s' % tmp_fn) 
                   1074:             if isfile(tmp_fn):
                   1075:                 if _debug: LOG(_SUBSYS, INFO, 'isfile %s' % tmp_fn) 
                   1076:                 fn = self._fsname(self.filename)
                   1077:                 try: os.remove(fn)
                   1078:                 except OSError: pass
                   1079:                 os.rename(tmp_fn, fn)
                   1080: 
                   1081:     def _abort(self):
                   1082:         """ Deletes the temporary file """
                   1083:         self._v_abort_called = 1    # for tests
                   1084:         TM.remove(self)             # for tests
                   1085:         if self.filename:
                   1086:             tmp_fn = self._temp_fsname(self.filename)
                   1087:             if _debug: LOG(_SUBSYS, INFO, 'aborting %s' % tmp_fn) 
                   1088:             if isfile(tmp_fn):
                   1089:                 if _debug: LOG(_SUBSYS, INFO, 'isfile %s' % tmp_fn) 
                   1090:                 try: os.remove(tmp_fn)
                   1091:                 except OSError: pass
                   1092: 
                   1093: InitializeClass(ExtFile)
                   1094: 
                   1095: 
                   1096: # Filename to id translation
                   1097: bad_chars =  """ ,;:'"()[]{}ÄÅÁÀÂÃäåáàâãÇçÉÈÊËÆéèêëæÍÌÎÏíìîïÑñÖÓÒÔÕØöóòôõøŠšßÜÚÙÛüúùûÝŸýÿŽž"""
                   1098: good_chars = """____________AAAAAAaaaaaaCcEEEEEeeeeeIIIIiiiiNnOOOOOOooooooSssUUUUuuuuYYyyZz"""
                   1099: TRANSMAP = string.maketrans(bad_chars, good_chars)
                   1100: 
                   1101: def normalize_id(id):
                   1102:     # Support at least utf-8 and latin-1 filenames.
                   1103:     # This is lame, but before it was latin-1 only.
                   1104:     try:
                   1105:         uid = unicode(id, 'utf-8')
                   1106:     except UnicodeError, TypeError:
                   1107:         try:
                   1108:             uid = unicode(id, 'iso-8859-15')
                   1109:         except UnicodeError, TypeError:
                   1110:             return id
                   1111:     id = uid.encode('iso-8859-15', 'ignore')
                   1112:     id = string.translate(id, TRANSMAP)
                   1113:     return id
                   1114: 
                   1115: 
                   1116: # FileUpload factory
                   1117: from cgi import FieldStorage
                   1118: from ZPublisher.HTTPRequest import FileUpload
                   1119: 
                   1120: def HTTPUpload(fp, content_type=None):
                   1121:     """ Create a FileUpload instance from a file handle (and content_type) """
                   1122:     if isinstance(fp, FileUpload):
                   1123:         if content_type:
                   1124:             fp.headers['content-type'] = content_type
                   1125:         return fp
                   1126:     else:
                   1127:         environ = {'REQUEST_METHOD': 'POST'}
                   1128:         if content_type:
                   1129:             environ['CONTENT_TYPE'] = content_type
                   1130:         elif hasattr(fp, 'headers') and fp.headers.has_key('content-type'):
                   1131:             environ['CONTENT_TYPE'] = fp.headers['content-type']
                   1132:         fs = FieldStorage(fp=fp, environ=environ)
                   1133:         return FileUpload(fs)
                   1134: 
                   1135: 
                   1136: # Repository lock
                   1137: import time
                   1138: 
                   1139: class DirLockError(OSError): 
                   1140:     pass
                   1141: 
                   1142: class DirLock:
                   1143:     """ Manage the lockfile for a directory """
                   1144: 
                   1145:     lock_name = '@@@lock'
                   1146:     sleep_secs = 1.5
                   1147:     sleep_times = 10
                   1148: 
                   1149:     def _mklock(self):
                   1150:         f = open(self._lock, 'wt') 
                   1151:         f.write('ExtFile dir lock. You may want to remove this file.')
                   1152:         f.close()
                   1153: 
                   1154:     def _rmlock(self):
                   1155:         os.remove(self._lock)
                   1156: 
                   1157:     def islocked(self):
                   1158:         os.path.isfile(self._lock)
                   1159: 
                   1160:     def release(self):
                   1161:         self._rmlock()
                   1162: 
                   1163:     def __init__(self, dir):
                   1164:         self._lock = os.path.join(dir, self.lock_name)
                   1165:         for i in range(self.sleep_times):
                   1166:             if self.islocked():
                   1167:                 LOG(_SUBSYS, BLATHER, "Waiting for lock '%s'" % self._lock)
                   1168:                 time.sleep(self.sleep_secs)
                   1169:             else:
                   1170:                 self._mklock()
                   1171:                 break
                   1172:         else:
                   1173:             LOG(_SUBSYS, BLATHER, "Failed to get lock '%s'" % self._lock)
                   1174:             raise DirLockError, "Failed to get lock '%s'" % self._lock
                   1175: 
                   1176: 
                   1177: # Stream iterator
                   1178: if IStreamIterator is not None:
                   1179: 
                   1180:     class stream_iterator:
                   1181:         __implements__ = (IStreamIterator,)
                   1182: 
                   1183:         def __init__(self, stream, blocksize=2<<16):
                   1184:             self._stream = stream
                   1185:             self._blocksize = blocksize
                   1186: 
                   1187:         def next(self):
                   1188:             data = self._stream.read(self._blocksize)
                   1189:             if not data:
                   1190:                 self._stream.close()
                   1191:                 self._stream = None
                   1192:                 raise StopIteration
                   1193:             return data
                   1194: 
                   1195:         def __len__(self):
                   1196:             cur_pos = self._stream.tell()
                   1197:             self._stream.seek(0, 2)
                   1198:             size = self._stream.tell()
                   1199:             self._stream.seek(cur_pos, 0)
                   1200:             return size
                   1201: 
                   1202:         def __nonzero__(self):
                   1203:             return self.__len__() and 1 or 0
                   1204: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>