BlockIt
library.py
Go to the documentation of this file.
00001 #................................................................................
00002 # The MIT License
00003 
00004 # Copyright (c) 2009 David Car, david.car7@gmail.com
00005 
00006 # Permission is hereby granted, free of charge, to any person obtaining a copy
00007 # of this software and associated documentation files (the "Software"), to deal
00008 # in the Software without restriction, including without limitation the rights
00009 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00010 # copies of the Software, and to permit persons to whom the Software is
00011 # furnished to do so, subject to the following conditions:
00012 
00013 # The above copyright notice and this permission notice shall be included in all
00014 # copies or substantial portions of the Software.
00015 
00016 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00017 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00018 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00019 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00020 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00021 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
00022 # SOFTWARE.
00023 #................................................................................
00024 
00025 __all__ = ['Library',
00026            'CacheManager']
00027 
00028 from tables import SymbolTable
00029 from scanners import LineScanner
00030 import shelve
00031 import os
00032 import sys
00033 import time
00034 import pickle
00035 import glob
00036 
00037 #................................................................................
00038 # Library helper functions
00039 #................................................................................
00040 
00041 #................................................................................
00042 class Library(object):
00043     '''This is a class which acts as a library to store/retrieve pre-parsed
00044     blocks.  It uses the Python shelve module for peristent storage.  It will
00045     also have the ability to store instantiated templates correlated with
00046     their parent file.
00047 
00048     '''
00049 
00050     VERSION = '1.1'
00051 
00052     def __init__(self, name, blockList):
00053         '''Library constructor
00054 
00055         Input
00056         -----
00057         name : library file name
00058         blockList : list of block types for use with library.  This is needed
00059                     for block reconstruction.
00060 
00061         '''
00062         
00063         self._name = name
00064         self._blockDict = {}
00065         for blk in blockList:
00066             self._blockDict[blk.BLOCKTYPE] = blk
00067         self._metadata = None
00068         self._lib = None
00069         self.open()
00070 
00071     def __getitem__(self, name):
00072         '''Return an item from the library.
00073 
00074         '''
00075         return self._lib.get(name, None)
00076 
00077     def __setitem__(self, name, item):
00078         '''Store an item in the library.  The item must be serializable.
00079 
00080         '''
00081         self._lib[name] = item
00082 
00083     def close(self):
00084         '''Close the database to make sure it is current.
00085 
00086         '''
00087         if self._lib is None: return
00088         try:
00089             self._metadata['LAST CLOSED'] = time.asctime()
00090             self._lib['METADATA'] = self._metadata
00091             self._lib.close()
00092         except:
00093             pass
00094 
00095     def open(self):
00096         '''Open the database.
00097 
00098         '''
00099         self.close()
00100         _newLib = not os.path.exists(self._name)
00101         self._lib = shelve.DbfilenameShelf(self._name)
00102         #................................................................................
00103         # Check the Library metadata
00104         #................................................................................
00105         _metadata = self._lib.get('METADATA', None)
00106         if _metadata:
00107             self._metadata = _metadata
00108             _ver = _metadata.get('VERSION', None)
00109         else:
00110             _ver = None
00111             self._metadata = {'VERSION': self.VERSION,
00112                               'LAST OPENED': time.asctime(),
00113                               'LAST CLOSED': time.asctime()}
00114             self._lib['METADATA'] = self._metadata
00115         if _newLib: return
00116 
00117         if _ver == None:
00118             print 'WARNING: Library missing version number'
00119             print 'MESSAGE: Storing version as %s'%(self.VERSION,)
00120             self._metadata['VERSION'] = self.VERSION
00121             self._lib['METADATA'] = self._metadata
00122         elif str(_ver) > self.VERSION:
00123             print 'WARNING: Library is newer than your version of BlockIt'
00124             print 'WARNING: Unknown behaviour can result'
00125             print '''MESSAGE: You may consider using a library tool or
00126         rebuilding the library'''
00127             print 'MESSAGE: Your version is %s'%(str(_ver),)
00128             print 'MESSAGE: Library class version is %s'%(self.VERSION,)
00129         elif str(_ver) < self.VERSION:
00130             print 'MESSAGE: Library is older than your version of BlockIt'
00131             print 'MESSAGE: Upgrading metadata'
00132             self._metadata['VERSION'] = self.VERSION
00133             self._upgradeLib()
00134 
00135         diff = 0
00136         try:
00137             lastOpened = self._metadata['LAST OPENED']
00138             lastClosed = self._metadata['LAST CLOSED']
00139             diff = time.mktime(time.strptime(lastOpened, '%a %b %d %H:%M:%S %Y'))-\
00140                 time.mktime(time.strptime(lastClosed, '%a %b %d %H:%M:%S %Y'))
00141         except:
00142             self._metadata['LAST CLOSED'] = time.asctime()
00143 
00144         if diff > 0:
00145             print '*'*80
00146             print 'WARNING: Library %s was not closed properly!'%(self._name,)
00147             print '*'*80
00148 
00149         self._metadata['LAST OPENED'] = time.asctime()
00150         self._lib['METADATA'] = self._metadata
00151 
00152     def keys(self):
00153         '''Wrapper to return the database keys.
00154 
00155         '''
00156         try:
00157             return self._lib.keys()
00158         except:
00159             return []
00160 
00161     def name(self):
00162         return self._name
00163 
00164     def fromFile(self, name, hsh=None):
00165         '''Read a block from a pickled block file.  This will restore everything
00166         necessary to for the block and all its children which was written
00167         using the blocks toFile method.
00168 
00169         Input
00170         -----
00171         name : block fileName to read from
00172         hsh : a hash to compare file hash with.  If the same, then pickle file is
00173               read, else None is returned.
00174 
00175         '''
00176         import pickle
00177 
00178         input = open(name+'.pkl','rb')
00179         _hsh, _tree = pickle.load(hsh_)
00180         if hsh is not None:
00181             if hsh != _hsh: return None
00182 
00183         input.close()
00184         return self.unpackTree(_tree)
00185 
00186     def unpackTree(self, tree):
00187         '''Unpack a dictionary tree of blocks and return parent block.
00188 
00189         '''
00190         _parent, _children = tree
00191         _params = _parent['_parameters']
00192         _blockType = _parent['_blockType']
00193         cons = self._blockDict.get(_blockType, None)
00194         if cons is None:
00195             print '''WARNING: Library does not have necessary constructor to
00196         build block "%s"'''%(_blockType,)
00197             return None
00198 
00199         parent = cons(_params)
00200         parent.__dict__.update(_parent)
00201 
00202         for node in _children:
00203             child = self.unpackTree(node)
00204             child.setParent(parent)
00205             parent._children.register(child)
00206 
00207         return parent
00208 
00209     def get( self, item, default ):
00210         try:
00211             value = self._lib[ item ]
00212         except:
00213             value = default
00214         return value
00215     
00216     def pop(self, item):
00217         try:
00218             return self._lib.pop(item)
00219         except:
00220             pass
00221         
00222     @staticmethod
00223     def genKey(blk):
00224         '''Static method to generate a library key from an object that has
00225         a fileName() or name() and hash() method.
00226 
00227         Input
00228         -----
00229         blk : object with a fileName() or name() and hash() method. fileName()
00230               is tried first, then name().
00231 
00232         '''
00233 
00234         name = getattr(blk, 'fileName', None) or getattr(blk, 'name', None)
00235         if name is not None:
00236             return os.path.join(name(), blk.hash())
00237         else:
00238             raise Exception('''Object does not support correct API to generate
00239         key name''')
00240 
00241     def _upgradeLib( self ):
00242         '''Function called when a library version number is less than the the
00243         current codes version number for the library.
00244 
00245         '''
00246         pass
00247 
00248     def __iadd__(self, other):
00249         '''Add contents of another library into this library using unary add,
00250         i.e. self += other.  
00251         '''
00252         try:
00253             self._lib.update(other._lib)
00254             self._blockDict.update(other._blockDict)
00255             self._lib['METADATA'] = self._metadata
00256         except:
00257             print "Error merging library %s into this library."%(other._name,)
00258 
00259         return self
00260 
00261 #================================================================================
00262 class CacheManager( object ):
00263     '''Class to manage cached (i.e. pickled) blocks.  Need to inherit from
00264     this class and add a genCacheName() method which takes a block and returns
00265     a tuple of (boolean, string).  See virtual method genCacheName() for details.
00266 
00267     '''
00268     def __init__( self, ext='.cache' ):
00269         self._ext = ext
00270 
00271     def fromCache( self, blk ):
00272         '''Check for a cached version of the given blk.  If there is and the
00273         block version number matches the passed in block version, then it is
00274         returned; else the cache is cleared and None is returned.
00275 
00276         '''
00277         _blk = None
00278         stat, cacheName = self.genCacheName( blk )
00279         newer, exists = stat
00280         if newer:
00281             try:
00282                 fd = open( cacheName, 'rb' )
00283                 version = fd.readline().rstrip()
00284                 if version == blk.version(): 
00285                     _blk = pickle.load( fd )
00286                 else:
00287                     fd.close()
00288                     self.clearCache( blk )
00289             except:
00290                 sys.stderr.write( 'Error reading cache for file %s%s' %
00291                                   (blk.name(), os.linesep) )
00292             finally:
00293                 fd.close()
00294         return _blk
00295 
00296     def fromCacheSerialized( self, blk ):
00297         '''Just read the pickled version of the block from the cache file if
00298         it exists and return it without unpickling.
00299 
00300         '''
00301         _blk = None
00302         stat, cacheName = self.genCacheName( blk )
00303         newer, exists = stat
00304         if newer:
00305             fd = open( cacheName, 'rb' )
00306             try:
00307                 version = fd.readline().rstrip()
00308                 if version == blk.version(): 
00309                     _blk = fd.read()
00310                 else:
00311                     fd.close()
00312                     self.clearCache( blk )
00313             except:
00314                 sys.stderr.write( 'Error reading cache for file %s%s' %
00315                                   (blk.name(), os.linesep) )
00316             finally:
00317                 fd.close()
00318         return _blk
00319 
00320     def unSerialize( self, serial ):
00321         '''Unserialize a cached file.  This is just a wrapper for pickle.loads
00322         method. 
00323 
00324         '''
00325         return pickle.loads( serial )
00326 
00327     def clearCache( self, blk ):
00328         '''Clear all caches related to the block.
00329 
00330         '''
00331         stat, cacheName = self.genCacheName( blk )
00332         newer, exists = stat
00333         if exists:
00334             dir = os.path.dirname( cacheName )
00335             for f in glob.glob( os.path.join( dir, '*' + self._ext ) ):
00336                 os.remove( f )
00337 
00338     def toCache( self, blk ):
00339         '''Write a blk to a cache file.
00340 
00341         '''
00342         stat, cacheName = self.genCacheName( blk )
00343         newer, exists = stat
00344         try:
00345             fd = open( cacheName, 'wb' )
00346             fromCache = blk.fromCache
00347             blk.fromCache = True
00348             fd.write( blk.version() + os.linesep )
00349             pickle.dump( blk, fd )
00350             blk.fromCache = fromCache
00351         except:
00352             sys.stderr.write( 'Error caching file %s%s' % 
00353                               (blk.name(), os.linesep) )
00354         finally:
00355             fd.close()
00356 
00357         return
00358 
00359     #................................................................................
00360     # Virtual Methods
00361     #................................................................................
00362     def genCacheName( self, blk ):
00363         '''Takes a block and returns a tuple.  The first element is a
00364         two-tuple with the fist elementa boolean on whether the cacheName is
00365         older than the originating blk (i.e. the provided argument), the
00366         second element is if the cacheName exists.  The second arg in the main
00367         tuple is the name of the cache file.  For example:
00368 
00369         ( ( true, true ), "myCache.pkl" ) - means that the cache file exists
00370         and is newer than the file that contains the passed in block and the
00371         name of the cache file is "myCache.pkl".
00372 
00373         '''
00374         raise NotImplementedError('''Virtual method 'genCacheName' in CacheManager
00375         class''')
00376 
00377 
00378     ext = property( fget=lambda x: x._ext )
 All Classes Namespaces Files Functions Variables Properties