vendor/CMF/1.6.3/CMFCore

view DirectoryView.py @ 2:4c712d7bd1d7

Added tag 1.6.3 for changeset 1babb9d61518
author Georges Racinet on purity.racinet.fr <georges@racinet.fr>
date Fri, 09 Sep 2011 12:44:00 +0200
parents
children
line source
1 ##############################################################################
2 #
3 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
4 #
5 # This software is subject to the provisions of the Zope Public License,
6 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10 # FOR A PARTICULAR PURPOSE.
11 #
12 ##############################################################################
13 """ Views of filesystem directories as folders.
15 $Id$
16 """
18 import re
19 from os import path, listdir, stat
20 from sys import exc_info
21 from sys import platform
22 import logging
23 from warnings import warn
25 from AccessControl import ClassSecurityInfo
26 from Acquisition import aq_inner, aq_parent
27 from Globals import DevelopmentMode
28 from Globals import DTMLFile
29 from Globals import HTMLFile
30 from Globals import InitializeClass
31 from Globals import package_home
32 from Globals import Persistent
33 from OFS.Folder import Folder
34 from OFS.ObjectManager import bad_id
35 from zope.interface import implements
37 from FSMetadata import FSMetadata
38 from FSObject import BadFile
39 from interfaces import IDirectoryView
40 from permissions import AccessContentsInformation
41 from permissions import ManagePortal
42 from utils import _dtmldir
43 from utils import expandpath as _new_expandpath
44 from utils import minimalpath
45 from utils import normalize
48 logger = logging.getLogger('CMFCore.DirectoryView')
51 def expandpath(p):
52 """ utils.expandpath() wrapper for backwards compatibility.
53 """
54 warn('expandpath() doesn\'t belong to DirectoryView anymore and will be '
55 'removed from that module in CMF 2.0. Please import expandpath from '
56 'the utils module.',
57 DeprecationWarning)
58 return _new_expandpath(p)
60 __reload_module__ = 0
62 # Ignore filesystem artifacts
63 base_ignore = ('.', '..')
64 # Ignore version control subdirectories
65 ignore = ('CVS', 'SVN', '.', '..', '.svn')
66 # Ignore suspected backups and hidden files
67 ignore_re = re.compile(r'\.|(.*~$)|#')
69 # and special names.
70 def _filtered_listdir(path, ignore=ignore):
71 return [ name
72 for name
73 in listdir(path)
74 if name not in ignore and not ignore_re.match(name) ]
76 class _walker:
77 def __init__(self, ignore=ignore):
78 # make a dict for faster lookup
79 self.ignore = dict([(x, None) for x in ignore])
81 def __call__(self, listdir, dirname, names):
82 # filter names inplace, so filtered directories don't get visited
83 names[:] = [ name
84 for name
85 in names
86 if name not in self.ignore and not ignore_re.match(name) ]
87 # append with stat info
88 results = [ (name, stat(path.join(dirname,name))[8])
89 for name in names ]
90 listdir.extend(results)
92 class DirectoryInformation:
93 data = None
94 _v_last_read = 0
95 _v_last_filelist = [] # Only used on Win32
97 def __init__(self, filepath, minimal_fp, ignore=ignore):
98 self._filepath = filepath
99 self._minimal_fp = minimal_fp
100 self.ignore=base_ignore + tuple(ignore)
101 if platform == 'win32':
102 self._walker = _walker(self.ignore)
103 subdirs = []
104 for entry in _filtered_listdir(self._filepath, ignore=self.ignore):
105 entry_filepath = path.join(self._filepath, entry)
106 if path.isdir(entry_filepath):
107 subdirs.append(entry)
108 self.subdirs = tuple(subdirs)
110 def getSubdirs(self):
111 return self.subdirs
113 def _isAllowableFilename(self, entry):
114 if entry[-1:] == '~':
115 return 0
116 if entry[:1] in ('_', '#'):
117 return 0
118 return 1
120 def reload(self):
121 self.data = None
123 def _readTypesFile(self):
124 """ Read the .objects file produced by FSDump.
125 """
126 types = {}
127 try:
128 f = open( path.join(self._filepath, '.objects'), 'rt' )
129 except IOError:
130 pass
131 else:
132 lines = f.readlines()
133 f.close()
134 for line in lines:
135 try:
136 obname, meta_type = line.split(':')
137 except ValueError:
138 pass
139 else:
140 types[obname.strip()] = meta_type.strip()
141 return types
143 if DevelopmentMode:
145 def _changed(self):
146 mtime=0
147 filelist=[]
148 try:
149 mtime = stat(self._filepath)[8]
150 if platform == 'win32':
151 # some Windows directories don't change mtime
152 # when a file is added to or deleted from them :-(
153 # So keep a list of files as well, and see if that
154 # changes
155 path.walk(self._filepath, self._walker, filelist)
156 filelist.sort()
157 except:
158 logger.exception("Error checking for directory modification")
160 if mtime != self._v_last_read or filelist != self._v_last_filelist:
161 self._v_last_read = mtime
162 self._v_last_filelist = filelist
164 return 1
166 return 0
168 else:
170 def _changed(self):
171 return 0
173 def getContents(self, registry):
174 changed = self._changed()
175 if self.data is None or changed:
176 try:
177 self.data, self.objects = self.prepareContents(registry,
178 register_subdirs=changed)
179 except:
180 logger.exception("Error during prepareContents")
181 self.data = {}
182 self.objects = ()
184 return self.data, self.objects
186 def prepareContents(self, registry, register_subdirs=0):
187 # Creates objects for each file.
188 data = {}
189 objects = []
190 types = self._readTypesFile()
191 for entry in _filtered_listdir(self._filepath, ignore=self.ignore):
192 if not self._isAllowableFilename(entry):
193 continue
194 entry_minimal_fp = '/'.join( (self._minimal_fp, entry) )
195 entry_filepath = path.join(self._filepath, entry)
196 if path.isdir(entry_filepath):
197 # Add a subdirectory only if it was previously registered,
198 # unless register_subdirs is set.
199 info = registry.getDirectoryInfo(entry_minimal_fp)
200 if info is None and register_subdirs:
201 # Register unknown subdirs
202 registry.registerDirectoryByPath(entry_filepath)
203 info = registry.getDirectoryInfo(entry_minimal_fp)
204 if info is not None:
205 mt = types.get(entry)
206 t = None
207 if mt is not None:
208 t = registry.getTypeByMetaType(mt)
209 if t is None:
210 t = DirectoryView
211 ob = t(entry, entry_minimal_fp)
212 ob_id = ob.getId()
213 data[ob_id] = ob
214 objects.append({'id': ob_id, 'meta_type': ob.meta_type})
215 else:
216 pos = entry.rfind('.')
217 if pos >= 0:
218 name = entry[:pos]
219 ext = path.normcase(entry[pos + 1:])
220 else:
221 name = entry
222 ext = ''
223 if not name or name == 'REQUEST':
224 # Not an allowable id.
225 continue
226 mo = bad_id(name)
227 if mo is not None and mo != -1: # Both re and regex formats
228 # Not an allowable id.
229 continue
230 t = None
231 mt = types.get(entry, None)
232 if mt is None:
233 mt = types.get(name, None)
234 if mt is not None:
235 t = registry.getTypeByMetaType(mt)
236 if t is None:
237 t = registry.getTypeByExtension(ext)
239 if t is not None:
240 metadata = FSMetadata(entry_filepath)
241 metadata.read()
242 try:
243 ob = t(name, entry_minimal_fp, fullname=entry,
244 properties=metadata.getProperties())
245 except:
246 import traceback
247 typ, val, tb = exc_info()
248 try:
249 logger.exception("prepareContents")
251 exc_lines = traceback.format_exception( typ,
252 val,
253 tb )
254 ob = BadFile( name,
255 entry_minimal_fp,
256 exc_str='\r\n'.join(exc_lines),
257 fullname=entry )
258 finally:
259 tb = None # Avoid leaking frame!
261 # FS-based security
262 permissions = metadata.getSecurity()
263 if permissions is not None:
264 for name in permissions.keys():
265 acquire, roles = permissions[name]
266 try:
267 ob.manage_permission(name,roles,acquire)
268 except ValueError:
269 logger.exception("Error setting permissions")
271 # only DTML Methods and Python Scripts can have proxy roles
272 if hasattr(ob, '_proxy_roles'):
273 try:
274 ob._proxy_roles = tuple(metadata.getProxyRoles())
275 except:
276 logger.exception("Error setting proxy role")
278 ob_id = ob.getId()
279 data[ob_id] = ob
280 objects.append({'id': ob_id, 'meta_type': ob.meta_type})
282 return data, tuple(objects)
285 class DirectoryRegistry:
287 def __init__(self):
288 self._meta_types = {}
289 self._object_types = {}
290 self._directories = {}
292 def registerFileExtension(self, ext, klass):
293 self._object_types[ext] = klass
295 def registerMetaType(self, mt, klass):
296 self._meta_types[mt] = klass
298 def getTypeByExtension(self, ext):
299 return self._object_types.get(ext, None)
301 def getTypeByMetaType(self, mt):
302 return self._meta_types.get(mt, None)
304 def registerDirectory(self, name, _prefix, subdirs=1, ignore=ignore):
305 # This what is actually called to register a
306 # file system directory to become a FSDV.
307 if not isinstance(_prefix, basestring):
308 _prefix = package_home(_prefix)
309 filepath = path.join(_prefix, name)
310 self.registerDirectoryByPath(filepath, subdirs, ignore=ignore)
312 def registerDirectoryByPath(self, filepath, subdirs=1, ignore=ignore):
313 # This is indirectly called during registration of
314 # a directory. As you can see, minimalpath is called
315 # on the supplied path at this point.
316 # The idea is that the registry will only contain
317 # small paths that are likely to work across platforms
318 # and SOFTWARE_HOME, INSTANCE_HOME and PRODUCTS_PATH setups
319 minimal_fp = minimalpath(filepath)
320 info = DirectoryInformation(filepath, minimal_fp, ignore=ignore)
321 self._directories[minimal_fp] = info
322 if subdirs:
323 for entry in info.getSubdirs():
324 entry_filepath = path.join(filepath, entry)
325 self.registerDirectoryByPath( entry_filepath
326 , subdirs
327 , ignore=ignore
328 )
330 def reloadDirectory(self, minimal_fp):
331 info = self.getDirectoryInfo(minimal_fp)
332 if info is not None:
333 info.reload()
335 def getDirectoryInfo(self, minimal_fp):
336 # This is called when we need to get hold of the information
337 # for a minimal path. Can return None.
338 return self._directories.get(minimal_fp, None)
340 def listDirectories(self):
341 dirs = self._directories.keys()
342 dirs.sort()
343 return dirs
346 _dirreg = DirectoryRegistry()
347 registerDirectory = _dirreg.registerDirectory
348 registerFileExtension = _dirreg.registerFileExtension
349 registerMetaType = _dirreg.registerMetaType
352 def listFolderHierarchy(ob, path, rval, adding_meta_type=None):
353 if not hasattr(ob, 'objectValues'):
354 return
355 values = ob.objectValues()
356 for subob in ob.objectValues():
357 base = getattr(subob, 'aq_base', subob)
358 if getattr(base, 'isPrincipiaFolderish', 0):
360 if adding_meta_type is not None and hasattr(
361 base, 'filtered_meta_types'):
362 # Include only if the user is allowed to
363 # add the given meta type in this location.
364 meta_types = subob.filtered_meta_types()
365 found = 0
366 for mt in meta_types:
367 if mt['name'] == adding_meta_type:
368 found = 1
369 break
370 if not found:
371 continue
373 if path:
374 subpath = path + '/' + subob.getId()
375 else:
376 subpath = subob.getId()
377 title = getattr(subob, 'title', None)
378 if title:
379 name = '%s (%s)' % (subpath, title)
380 else:
381 name = subpath
382 rval.append((subpath, name))
383 listFolderHierarchy(subob, subpath, rval, adding_meta_type)
386 class DirectoryView (Persistent):
387 """ Directory views mount filesystem directories.
388 """
390 implements(IDirectoryView)
392 meta_type = 'Filesystem Directory View'
393 _dirpath = None
394 _objects = ()
396 def __init__(self, id, dirpath='', fullname=None):
397 self.id = id
398 self._dirpath = dirpath
400 def __of__(self, parent):
401 dirpath = self._dirpath
402 info = _dirreg.getDirectoryInfo(dirpath)
403 if info is None:
404 # for DirectoryViews created with CMF versions before 1.5
405 # this is basically the old minimalpath() code
406 dirpath = normalize(dirpath)
407 index = dirpath.rfind('Products')
408 if index == -1:
409 index = dirpath.rfind('products')
410 if index != -1:
411 dirpath = dirpath[index+len('products/'):]
412 info = _dirreg.getDirectoryInfo(dirpath)
413 if info is not None:
414 # update the directory view with a corrected path
415 self._dirpath = dirpath
416 elif self._dirpath:
417 warn('DirectoryView %s refers to a non-existing path %s'
418 % (self.id, dirpath), UserWarning)
419 if info is None:
420 data = {}
421 objects = ()
422 else:
423 data, objects = info.getContents(_dirreg)
424 s = DirectoryViewSurrogate(self, data, objects)
425 res = s.__of__(parent)
426 return res
428 def getId(self):
429 return self.id
431 InitializeClass(DirectoryView)
434 class DirectoryViewSurrogate (Folder):
435 """ Folderish DirectoryView.
436 """
438 implements(IDirectoryView)
440 meta_type = 'Filesystem Directory View'
441 all_meta_types = ()
442 _isDirectoryView = 1
444 # _is_wrapperish = 1
446 security = ClassSecurityInfo()
448 def __init__(self, real, data, objects):
449 d = self.__dict__
450 d.update(data)
451 d.update(real.__dict__)
452 d['_real'] = real
453 d['_objects'] = objects
455 def __setattr__(self, name, value):
456 d = self.__dict__
457 d[name] = value
458 setattr(d['_real'], name, value)
460 def __delattr__(self, name):
461 d = self.__dict__
462 del d[name]
463 delattr(d['_real'], name)
465 security.declareProtected(ManagePortal, 'manage_propertiesForm')
466 manage_propertiesForm = DTMLFile( 'dirview_properties', _dtmldir )
468 security.declareProtected(ManagePortal, 'manage_properties')
469 def manage_properties( self, dirpath, REQUEST=None ):
470 """ Update the directory path of the DirectoryView.
471 """
472 self.__dict__['_real']._dirpath = dirpath
473 if REQUEST is not None:
474 REQUEST['RESPONSE'].redirect( '%s/manage_propertiesForm'
475 % self.absolute_url() )
477 security.declareProtected(AccessContentsInformation, 'getCustomizableObject')
478 def getCustomizableObject(self):
479 ob = aq_parent(aq_inner(self))
480 while getattr(ob, '_isDirectoryView', 0):
481 ob = aq_parent(aq_inner(ob))
482 return ob
484 security.declareProtected(AccessContentsInformation, 'listCustFolderPaths')
485 def listCustFolderPaths(self, adding_meta_type=None):
486 """ List possible customization folders as key, value pairs.
487 """
488 rval = []
489 ob = self.getCustomizableObject()
490 listFolderHierarchy(ob, '', rval, adding_meta_type)
491 rval.sort()
492 return rval
494 security.declareProtected(AccessContentsInformation, 'getDirPath')
495 def getDirPath(self):
496 return self.__dict__['_real']._dirpath
498 security.declarePublic('getId')
499 def getId(self):
500 return self.id
502 InitializeClass(DirectoryViewSurrogate)
505 manage_addDirectoryViewForm = HTMLFile('dtml/addFSDirView', globals())
507 def createDirectoryView(parent, minimal_fp, id=None):
508 """ Add either a DirectoryView or a derivative object.
509 """
510 info = _dirreg.getDirectoryInfo(minimal_fp)
511 if info is None:
512 fixed_minimal_fp = minimal_fp.replace('\\','/')
513 info = _dirreg.getDirectoryInfo(fixed_minimal_fp)
514 if info is None:
515 raise ValueError('Not a registered directory: %s' % minimal_fp)
516 else:
517 warn('createDirectoryView() expects a slash-separated path '
518 'relative to the Products path. \'%s\' will no longer work '
519 'in CMF 2.0.' % minimal_fp,
520 DeprecationWarning)
521 minimal_fp = fixed_minimal_fp
522 if not id:
523 id = minimal_fp.split('/')[-1]
524 else:
525 id = str(id)
526 ob = DirectoryView(id, minimal_fp)
527 parent._setObject(id, ob)
529 def addDirectoryViews(ob, name, _prefix):
530 """ Add a directory view for every subdirectory of the given directory.
532 Meant to be called by filesystem-based code. Note that registerDirectory()
533 still needs to be called by product initialization code to satisfy
534 persistence demands.
535 """
536 if not isinstance(_prefix, basestring):
537 _prefix = package_home(_prefix)
538 filepath = path.join(_prefix, name)
539 minimal_fp = minimalpath(filepath)
540 info = _dirreg.getDirectoryInfo(minimal_fp)
541 if info is None:
542 raise ValueError('Not a registered directory: %s' % minimal_fp)
543 for entry in info.getSubdirs():
544 entry_minimal_fp = '/'.join( (minimal_fp, entry) )
545 createDirectoryView(ob, entry_minimal_fp, entry)
547 def manage_addDirectoryView(self, dirpath, id=None, REQUEST=None):
548 """ Add either a DirectoryView or a derivative object.
549 """
550 createDirectoryView(self, dirpath, id)
551 if REQUEST is not None:
552 return self.manage_main(self, REQUEST)
554 def manage_listAvailableDirectories(*args):
555 """ List registered directories.
556 """
557 return list(_dirreg.listDirectories())