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>