Annotation of ExtFile/ExtFile.py, revision 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>