Back to index

plone3  3.1.7
context.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """ Various context implementations for export / import of configurations.
00014 
00015 Wrappers representing the state of an import / export operation.
00016 
00017 $Id: context.py 78358 2007-07-26 16:59:12Z yuppie $
00018 """
00019 
00020 import logging
00021 import os
00022 import time
00023 from StringIO import StringIO
00024 from tarfile import TarFile
00025 from tarfile import TarInfo
00026 
00027 from AccessControl import ClassSecurityInfo
00028 from Acquisition import aq_inner
00029 from Acquisition import aq_parent
00030 from Acquisition import aq_self
00031 from Acquisition import Implicit
00032 from DateTime.DateTime import DateTime
00033 from Globals import InitializeClass
00034 from OFS.DTMLDocument import DTMLDocument
00035 from OFS.Folder import Folder
00036 from OFS.Image import File
00037 from OFS.Image import Image
00038 from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
00039 from Products.PythonScripts.PythonScript import PythonScript
00040 from zope.interface import implements
00041 
00042 from interfaces import IExportContext
00043 from interfaces import IImportContext
00044 from interfaces import ISetupEnviron
00045 from interfaces import IWriteLogger
00046 from interfaces import SKIPPED_FILES
00047 from interfaces import SKIPPED_SUFFIXES
00048 from permissions import ManagePortal
00049 
00050 
00051 class Logger:
00052 
00053     implements(IWriteLogger)
00054 
00055     def __init__(self, id, messages):
00056         """Initialize the logger with a name and an optional level.
00057         """
00058         self._id = id
00059         self._messages = messages
00060         self._logger = logging.getLogger('GenericSetup.%s' % id)
00061 
00062     def debug(self, msg, *args, **kwargs):
00063         """Log 'msg % args' with severity 'DEBUG'.
00064         """
00065         self.log(logging.DEBUG, msg, *args, **kwargs)
00066 
00067     def info(self, msg, *args, **kwargs):
00068         """Log 'msg % args' with severity 'INFO'.
00069         """
00070         self.log(logging.INFO, msg, *args, **kwargs)
00071 
00072     def warning(self, msg, *args, **kwargs):
00073         """Log 'msg % args' with severity 'WARNING'.
00074         """
00075         self.log(logging.WARNING, msg, *args, **kwargs)
00076 
00077     def error(self, msg, *args, **kwargs):
00078         """Log 'msg % args' with severity 'ERROR'.
00079         """
00080         self.log(logging.ERROR, msg, *args, **kwargs)
00081 
00082     def exception(self, msg, *args):
00083         """Convenience method for logging an ERROR with exception information.
00084         """
00085         self.error(msg, *args, **{'exc_info': 1})
00086 
00087     def critical(self, msg, *args, **kwargs):
00088         """Log 'msg % args' with severity 'CRITICAL'.
00089         """
00090         self.log(logging.CRITICAL, msg, *args, **kwargs)
00091 
00092     def log(self, level, msg, *args, **kwargs):
00093         """Log 'msg % args' with the integer severity 'level'.
00094         """
00095         self._messages.append((level, self._id, msg))
00096         self._logger.log(level, msg, *args, **kwargs)
00097 
00098 
00099 class SetupEnviron(Implicit):
00100 
00101     """Context for body im- and exporter.
00102     """
00103 
00104     implements(ISetupEnviron)
00105 
00106     security = ClassSecurityInfo()
00107 
00108     def __init__(self):
00109         self._should_purge = True
00110 
00111     security.declareProtected(ManagePortal, 'getLogger')
00112     def getLogger(self, name):
00113         """Get a logger with the specified name, creating it if necessary.
00114         """
00115         return logging.getLogger('GenericSetup.%s' % name)
00116 
00117     security.declareProtected(ManagePortal, 'shouldPurge')
00118     def shouldPurge(self):
00119         """When installing, should the existing setup be purged?
00120         """
00121         return self._should_purge
00122 
00123 
00124 class BaseContext(SetupEnviron):
00125 
00126     security = ClassSecurityInfo()
00127 
00128     def __init__( self, tool, encoding ):
00129 
00130         self._tool = tool
00131         self._site = aq_parent( aq_inner( tool ) )
00132         self._loggers = {}
00133         self._messages = []
00134         self._encoding = encoding
00135         self._should_purge = True
00136 
00137     security.declareProtected( ManagePortal, 'getSite' )
00138     def getSite( self ):
00139 
00140         """ See ISetupContext.
00141         """
00142         return aq_self(self._site)
00143 
00144     security.declareProtected( ManagePortal, 'getSetupTool' )
00145     def getSetupTool( self ):
00146 
00147         """ See ISetupContext.
00148         """
00149         return self._tool
00150 
00151     security.declareProtected( ManagePortal, 'getEncoding' )
00152     def getEncoding( self ):
00153 
00154         """ See ISetupContext.
00155         """
00156         return self._encoding
00157 
00158     security.declareProtected( ManagePortal, 'getLogger' )
00159     def getLogger( self, name ):
00160         """ See ISetupContext.
00161         """
00162         return self._loggers.setdefault(name, Logger(name, self._messages))
00163 
00164     security.declareProtected( ManagePortal, 'listNotes' )
00165     def listNotes(self):
00166 
00167         """ See ISetupContext.
00168         """
00169         return self._messages[:]
00170 
00171     security.declareProtected( ManagePortal, 'clearNotes' )
00172     def clearNotes(self):
00173 
00174         """ See ISetupContext.
00175         """
00176         self._messages[:] = []
00177 
00178 
00179 class DirectoryImportContext( BaseContext ):
00180 
00181     implements(IImportContext)
00182 
00183     security = ClassSecurityInfo()
00184 
00185     def __init__( self
00186                 , tool
00187                 , profile_path
00188                 , should_purge=False
00189                 , encoding=None
00190                 ):
00191 
00192         BaseContext.__init__( self, tool, encoding )
00193         self._profile_path = profile_path
00194         self._should_purge = bool( should_purge )
00195 
00196     security.declareProtected( ManagePortal, 'readDataFile' )
00197     def readDataFile( self, filename, subdir=None ):
00198 
00199         """ See IImportContext.
00200         """
00201         if subdir is None:
00202             full_path = os.path.join( self._profile_path, filename )
00203         else:
00204             full_path = os.path.join( self._profile_path, subdir, filename )
00205 
00206         if not os.path.exists( full_path ):
00207             return None
00208 
00209         file = open( full_path, 'rb' )
00210         result = file.read()
00211         file.close()
00212 
00213         return result
00214 
00215     security.declareProtected( ManagePortal, 'getLastModified' )
00216     def getLastModified( self, path ):
00217 
00218         """ See IImportContext.
00219         """
00220         full_path = os.path.join( self._profile_path, path )
00221 
00222         if not os.path.exists( full_path ):
00223             return None
00224 
00225         return DateTime( os.path.getmtime( full_path ) )
00226 
00227     security.declareProtected( ManagePortal, 'isDirectory' )
00228     def isDirectory( self, path ):
00229 
00230         """ See IImportContext.
00231         """
00232         full_path = os.path.join( self._profile_path, path )
00233 
00234         if not os.path.exists( full_path ):
00235             return None
00236 
00237         return os.path.isdir( full_path )
00238 
00239     security.declareProtected( ManagePortal, 'listDirectory' )
00240     def listDirectory(self, path, skip=SKIPPED_FILES,
00241                       skip_suffixes=SKIPPED_SUFFIXES):
00242 
00243         """ See IImportContext.
00244         """
00245         if path is None:
00246             path = ''
00247 
00248         full_path = os.path.join( self._profile_path, path )
00249 
00250         if not os.path.exists( full_path ) or not os.path.isdir( full_path ):
00251             return None
00252 
00253         names = []
00254         for name in os.listdir(full_path):
00255             if name in skip:
00256                 continue
00257             if [s for s in skip_suffixes if name.endswith(s)]:
00258                 continue
00259             names.append(name)
00260 
00261         return names
00262 
00263 InitializeClass( DirectoryImportContext )
00264 
00265 
00266 class DirectoryExportContext( BaseContext ):
00267 
00268     implements(IExportContext)
00269 
00270     security = ClassSecurityInfo()
00271 
00272     def __init__( self, tool, profile_path, encoding=None ):
00273 
00274         BaseContext.__init__( self, tool, encoding )
00275         self._profile_path = profile_path
00276 
00277     security.declareProtected( ManagePortal, 'writeDataFile' )
00278     def writeDataFile( self, filename, text, content_type, subdir=None ):
00279 
00280         """ See IExportContext.
00281         """
00282         if subdir is None:
00283             prefix = self._profile_path
00284         else:
00285             prefix = os.path.join( self._profile_path, subdir )
00286 
00287         full_path = os.path.join( prefix, filename )
00288 
00289         if not os.path.exists( prefix ):
00290             os.makedirs( prefix )
00291 
00292         mode = content_type.startswith( 'text/' ) and 'w' or 'wb'
00293 
00294         file = open( full_path, mode )
00295         file.write( text )
00296         file.close()
00297 
00298 InitializeClass( DirectoryExportContext )
00299 
00300 
00301 class TarballImportContext( BaseContext ):
00302 
00303     implements(IImportContext)
00304 
00305     security = ClassSecurityInfo()
00306 
00307     def __init__( self, tool, archive_bits, encoding=None,
00308                   should_purge=False ):
00309         BaseContext.__init__( self, tool, encoding )
00310         timestamp = time.gmtime()
00311         self._archive_stream = StringIO(archive_bits)
00312         self._archive = TarFile.open( 'foo.bar', 'r:gz'
00313                                     , self._archive_stream )
00314         self._should_purge = bool( should_purge )
00315 
00316     def readDataFile( self, filename, subdir=None ):
00317 
00318         """ See IImportContext.
00319         """
00320         if subdir is not None:
00321             filename = '/'.join( ( subdir, filename ) )
00322 
00323         try:
00324             file = self._archive.extractfile( filename )
00325         except KeyError:
00326             return None
00327 
00328         return file.read()
00329 
00330     def getLastModified( self, path ):
00331 
00332         """ See IImportContext.
00333         """
00334         info = self._getTarInfo( path )
00335         return info and info.mtime or None
00336 
00337     def isDirectory( self, path ):
00338 
00339         """ See IImportContext.
00340         """
00341         info = self._getTarInfo( path )
00342 
00343         if info is not None:
00344             return info.isdir()
00345 
00346     def listDirectory(self, path, skip=SKIPPED_FILES,
00347                       skip_suffixes=SKIPPED_SUFFIXES):
00348 
00349         """ See IImportContext.
00350         """
00351         if path is None:  # root is special case:  no leading '/'
00352             path = ''
00353         else:
00354             if not self.isDirectory(path):
00355                 return None
00356 
00357             if path[-1] != '/':
00358                 path = path + '/'
00359 
00360         pfx_len = len(path)
00361 
00362         names = []
00363         for name in self._archive.getnames():
00364             if name == path or not name.startswith(path):
00365                 continue
00366             name = name[pfx_len:]
00367             if '/' in name or name in skip:
00368                 continue
00369             if [s for s in skip_suffixes if name.endswith(s)]:
00370                 continue
00371             names.append(name)
00372 
00373         return names
00374 
00375     def shouldPurge( self ):
00376 
00377         """ See IImportContext.
00378         """
00379         return self._should_purge
00380 
00381     def _getTarInfo( self, path ):
00382         if path[-1] == '/':
00383             path = path[:-1]
00384         try:
00385             return self._archive.getmember( path )
00386         except KeyError:
00387             pass
00388         try:
00389             return self._archive.getmember( path + '/' )
00390         except KeyError:
00391             return None
00392 
00393 
00394 class TarballExportContext( BaseContext ):
00395 
00396     implements(IExportContext)
00397 
00398     security = ClassSecurityInfo()
00399 
00400     def __init__( self, tool, encoding=None ):
00401 
00402         BaseContext.__init__( self, tool, encoding )
00403 
00404         timestamp = time.gmtime()
00405         archive_name = ( 'setup_tool-%4d%02d%02d%02d%02d%02d.tar.gz'
00406                        % timestamp[:6] )
00407 
00408         self._archive_stream = StringIO()
00409         self._archive_filename = archive_name
00410         self._archive = TarFile.open( archive_name, 'w:gz'
00411                                     , self._archive_stream )
00412 
00413     security.declareProtected( ManagePortal, 'writeDataFile' )
00414     def writeDataFile( self, filename, text, content_type, subdir=None ):
00415 
00416         """ See IExportContext.
00417         """
00418         if subdir is not None:
00419             filename = '/'.join( ( subdir, filename ) )
00420 
00421         stream = StringIO( text )
00422         info = TarInfo( filename )
00423         info.size = len( text )
00424         info.mtime = time.time()
00425         self._archive.addfile( info, stream )
00426 
00427     security.declareProtected( ManagePortal, 'getArchive' )
00428     def getArchive( self ):
00429 
00430         """ Close the archive, and return it as a big string.
00431         """
00432         self._archive.close()
00433         return self._archive_stream.getvalue()
00434 
00435     security.declareProtected( ManagePortal, 'getArchiveFilename' )
00436     def getArchiveFilename( self ):
00437 
00438         """ Close the archive, and return it as a big string.
00439         """
00440         return self._archive_filename
00441 
00442 InitializeClass( TarballExportContext )
00443 
00444 
00445 class SnapshotExportContext( BaseContext ):
00446 
00447     implements(IExportContext)
00448 
00449     security = ClassSecurityInfo()
00450 
00451     def __init__( self, tool, snapshot_id, encoding=None ):
00452 
00453         BaseContext.__init__( self, tool, encoding )
00454         self._snapshot_id = snapshot_id
00455 
00456     security.declareProtected( ManagePortal, 'writeDataFile' )
00457     def writeDataFile( self, filename, text, content_type, subdir=None ):
00458 
00459         """ See IExportContext.
00460         """
00461         if subdir is not None:
00462             filename = '/'.join( ( subdir, filename ) )
00463 
00464         sep = filename.rfind('/')
00465         if sep != -1:
00466             subdir = filename[:sep]
00467             filename = filename[sep+1:]
00468         folder = self._ensureSnapshotsFolder( subdir )
00469 
00470         # TODO: switch on content_type
00471         ob = self._createObjectByType( filename, text, content_type )
00472         folder._setObject( str( filename ), ob ) # No Unicode IDs!
00473 
00474     security.declareProtected( ManagePortal, 'getSnapshotURL' )
00475     def getSnapshotURL( self ):
00476 
00477         """ See IExportContext.
00478         """
00479         return '%s/%s' % ( self._tool.absolute_url(), self._snapshot_id )
00480 
00481     security.declareProtected( ManagePortal, 'getSnapshotFolder' )
00482     def getSnapshotFolder( self ):
00483 
00484         """ See IExportContext.
00485         """
00486         return self._ensureSnapshotsFolder()
00487 
00488     #
00489     #   Helper methods
00490     #
00491     security.declarePrivate( '_createObjectByType' )
00492     def _createObjectByType( self, name, body, content_type ):
00493 
00494         if isinstance( body, unicode ):
00495             encoding = self.getEncoding()
00496             if encoding is None:
00497                 body = body.encode()
00498             else:
00499                 body = body.encode( encoding )
00500 
00501         if name.endswith('.py'):
00502 
00503             ob = PythonScript( name )
00504             ob.write( body )
00505 
00506         elif name.endswith('.dtml'):
00507 
00508             ob = DTMLDocument( '', __name__=name )
00509             ob.munge( body )
00510 
00511         elif content_type in ('text/html', 'text/xml' ):
00512 
00513             ob = ZopePageTemplate( name, body
00514                                  , content_type=content_type )
00515 
00516         elif content_type[:6]=='image/':
00517 
00518             ob=Image( name, '', body, content_type=content_type )
00519 
00520         else:
00521             ob=File( name, '', body, content_type=content_type )
00522 
00523         return ob
00524 
00525     security.declarePrivate( '_ensureSnapshotsFolder' )
00526     def _ensureSnapshotsFolder( self, subdir=None ):
00527 
00528         """ Ensure that the appropriate snapshot folder exists.
00529         """
00530         path = [ 'snapshots', self._snapshot_id ]
00531 
00532         if subdir is not None:
00533             path.extend( subdir.split( '/' ) )
00534 
00535         current = self._tool
00536 
00537         for element in path:
00538 
00539             if element not in current.objectIds():
00540                 # No Unicode IDs!
00541                 current._setObject( str( element ), Folder( element ) )
00542 
00543             current = current._getOb( element )
00544 
00545         return current
00546 
00547 InitializeClass( SnapshotExportContext )
00548 
00549 
00550 class SnapshotImportContext( BaseContext ):
00551 
00552     implements(IImportContext)
00553 
00554     security = ClassSecurityInfo()
00555 
00556     def __init__( self
00557                 , tool
00558                 , snapshot_id
00559                 , should_purge=False
00560                 , encoding=None
00561                 ):
00562 
00563         BaseContext.__init__( self, tool, encoding )
00564         self._snapshot_id = snapshot_id
00565         self._encoding = encoding
00566         self._should_purge = bool( should_purge )
00567 
00568     security.declareProtected( ManagePortal, 'readDataFile' )
00569     def readDataFile( self, filename, subdir=None ):
00570 
00571         """ See IImportContext.
00572         """
00573         if subdir is not None:
00574             filename = '/'.join( ( subdir, filename ) )
00575 
00576         sep = filename.rfind('/')
00577         if sep != -1:
00578             subdir = filename[:sep]
00579             filename = filename[sep+1:]
00580         try:
00581             snapshot = self._getSnapshotFolder( subdir )
00582             object = snapshot._getOb( filename )
00583         except ( AttributeError, KeyError ):
00584             return None
00585 
00586         try:
00587             data = object.read()
00588             if isinstance(data, unicode):
00589                 data = data.encode('utf-8')
00590             return data
00591         except AttributeError:
00592             return object.manage_FTPget()
00593 
00594     security.declareProtected( ManagePortal, 'getLastModified' )
00595     def getLastModified( self, path ):
00596 
00597         """ See IImportContext.
00598         """
00599         try:
00600             snapshot = self._getSnapshotFolder()
00601             object = snapshot.restrictedTraverse( path )
00602         except ( AttributeError, KeyError ):
00603             return None
00604         else:
00605             return object.bobobase_modification_time()
00606 
00607     security.declareProtected( ManagePortal, 'isDirectory' )
00608     def isDirectory( self, path ):
00609 
00610         """ See IImportContext.
00611         """
00612         try:
00613             snapshot = self._getSnapshotFolder()
00614             object = snapshot.restrictedTraverse( str( path ) )
00615         except ( AttributeError, KeyError ):
00616             return None
00617         else:
00618             folderish = getattr( object, 'isPrincipiaFolderish', False )
00619             return bool( folderish )
00620 
00621     security.declareProtected( ManagePortal, 'listDirectory' )
00622     def listDirectory(self, path, skip=(), skip_suffixes=()):
00623 
00624         """ See IImportContext.
00625         """
00626         try:
00627             snapshot = self._getSnapshotFolder()
00628             subdir = snapshot.restrictedTraverse( path )
00629         except ( AttributeError, KeyError ):
00630             return None
00631         else:
00632             if not getattr( subdir, 'isPrincipiaFolderish', False ):
00633                 return None
00634 
00635             names = []
00636             for name in subdir.objectIds():
00637                 if name in skip:
00638                     continue
00639                 if [s for s in skip_suffixes if name.endswith(s)]:
00640                     continue
00641                 names.append(name)
00642 
00643             return names
00644 
00645     security.declareProtected( ManagePortal, 'shouldPurge' )
00646     def shouldPurge( self ):
00647 
00648         """ See IImportContext.
00649         """
00650         return self._should_purge
00651 
00652     #
00653     #   Helper methods
00654     #
00655     security.declarePrivate( '_getSnapshotFolder' )
00656     def _getSnapshotFolder( self, subdir=None ):
00657 
00658         """ Return the appropriate snapshot (sub)folder.
00659         """
00660         path = [ 'snapshots', self._snapshot_id ]
00661 
00662         if subdir is not None:
00663             path.extend( subdir.split( '/' ) )
00664 
00665         return self._tool.restrictedTraverse( path )
00666 
00667 InitializeClass( SnapshotImportContext )