BlockIt
|
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 )