File:  [Repository] / ExtFile / ExtFile.py
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Jan 24 16:53:50 2007 UTC (18 years, 6 months ago) by dwinter
Branches: first, MAIN
CVS tags: release, HEAD
Auf der Basis http://www.zope.org/Members/shh/ExtFile Version 1.5.4

mit zlog ersetzt durch logging


    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>