| #!/usr/bin/python | 
 | # Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file | 
 | # for details. All rights reserved. Use of this source code is governed by a | 
 | # BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | """Module to manage IDL files.""" | 
 |  | 
 | import copy | 
 | import pickle | 
 | import logging | 
 | import os | 
 | import os.path | 
 | import shutil | 
 | import idlnode | 
 | import idlparser | 
 | import idlrenderer | 
 |  | 
 | _logger = logging.getLogger('database') | 
 |  | 
 |  | 
 | class Database(object): | 
 |   """The Database class manages a collection of IDL files stored | 
 |   inside a directory. | 
 |  | 
 |   Each IDL is describing a single interface. The IDL files are written in the | 
 |   FremontCut syntax, which is derived from the Web IDL syntax and includes | 
 |   annotations. | 
 |  | 
 |   Database operations include adding, updating and removing IDL files. | 
 |   """ | 
 |  | 
 |   def __init__(self, root_dir): | 
 |     """Initializes a Database over a given directory. | 
 |  | 
 |     Args: | 
 |       root_dir -- a directory. If directory does not exist, it will | 
 |       be created. | 
 |     """ | 
 |     self._root_dir = root_dir | 
 |     if not os.path.exists(root_dir): | 
 |       _logger.debug('creating root directory %s' % root_dir) | 
 |       os.makedirs(root_dir) | 
 |     self._all_interfaces = {} | 
 |     self._interfaces_to_delete = [] | 
 |     self._idlparser = idlparser.IDLParser(idlparser.FREMONTCUT_SYNTAX) | 
 |  | 
 |   def Clone(self): | 
 |     new_database = Database(self._root_dir) | 
 |     new_database._all_interfaces = copy.deepcopy(self._all_interfaces) | 
 |     new_database._interfaces_to_delete = copy.deepcopy( | 
 |         self._interfaces_to_delete) | 
 |     return new_database | 
 |  | 
 |   def Delete(self): | 
 |     """Deletes the database by deleting its directory""" | 
 |     if os.path.exists(self._root_dir): | 
 |       shutil.rmtree(self._root_dir) | 
 |     # reset in-memory constructs | 
 |     self._all_interfaces = {} | 
 |  | 
 |   def _ScanForInterfaces(self): | 
 |     """Iteratores over the database files and lists all interface names. | 
 |  | 
 |     Return: | 
 |       A list of interface names. | 
 |     """ | 
 |     res = [] | 
 |  | 
 |     def Visitor(_, dirname, names): | 
 |       for name in names: | 
 |         if os.path.isfile(os.path.join(dirname, name)): | 
 |           root, ext = os.path.splitext(name) | 
 |           if ext == '.idl': | 
 |             res.append(root) | 
 |  | 
 |     os.path.walk(self._root_dir, Visitor, None) | 
 |     return res | 
 |  | 
 |   def _FilePath(self, interface_name): | 
 |     """Calculates the file path that a given interface should | 
 |     be saved to. | 
 |  | 
 |     Args: | 
 |       interface_name -- the name of the interface. | 
 |     """ | 
 |     return os.path.join(self._root_dir, '%s.idl' % interface_name) | 
 |  | 
 |   def _LoadInterfaceFile(self, interface_name): | 
 |     """Loads an interface from the database. | 
 |  | 
 |     Returns: | 
 |       An IDLInterface instance or None if the interface is not found. | 
 |     Args: | 
 |       interface_name -- the name of the interface. | 
 |     """ | 
 |     file_name = self._FilePath(interface_name) | 
 |     _logger.info('loading %s' % file_name) | 
 |     if not os.path.exists(file_name): | 
 |       return None | 
 |  | 
 |     f = open(file_name, 'r') | 
 |     content = f.read() | 
 |     f.close() | 
 |  | 
 |     # Parse file: | 
 |     idl_file = idlnode.IDLFile(self._idlparser.parse(content), file_name) | 
 |  | 
 |     if not idl_file.interfaces: | 
 |       raise RuntimeError('No interface found in %s' % file_name) | 
 |     elif len(idl_file.interfaces) > 1: | 
 |       raise RuntimeError('Expected one interface in %s' % file_name) | 
 |  | 
 |     interface = idl_file.interfaces[0] | 
 |     self._all_interfaces[interface_name] = interface | 
 |     return interface | 
 |  | 
 |   def Load(self): | 
 |     """Loads all interfaces into memory. | 
 |     """ | 
 |     # FIXME: Speed this up by multi-threading. | 
 |     for (interface_name) in self._ScanForInterfaces(): | 
 |       self._LoadInterfaceFile(interface_name) | 
 |     self.Cache() | 
 |  | 
 |   def Cache(self): | 
 |     """Serialize the database using pickle for faster startup in the future | 
 |     """ | 
 |     output_file = open(os.path.join(self._root_dir, 'cache.pickle'), 'wb') | 
 |     pickle.dump(self._all_interfaces, output_file) | 
 |     pickle.dump(self._interfaces_to_delete, output_file) | 
 |  | 
 |   def LoadFromCache(self): | 
 |     """Deserialize the database using pickle for fast startup | 
 |     """ | 
 |     input_file_name = os.path.join(self._root_dir, 'cache.pickle') | 
 |     if not os.path.isfile(input_file_name): | 
 |       self.Load() | 
 |       return | 
 |     input_file = open(input_file_name, 'rb') | 
 |     self._all_interfaces = pickle.load(input_file) | 
 |     self._interfaces_to_delete = pickle.load(input_file) | 
 |     input_file.close() | 
 |  | 
 |   def Save(self): | 
 |     """Saves all in-memory interfaces into files.""" | 
 |     for interface in self._all_interfaces.values(): | 
 |       self._SaveInterfaceFile(interface) | 
 |     for interface_name in self._interfaces_to_delete: | 
 |       self._DeleteInterfaceFile(interface_name) | 
 |  | 
 |   def _SaveInterfaceFile(self, interface): | 
 |     """Saves an interface into the database. | 
 |  | 
 |     Args: | 
 |       interface -- an IDLInterface instance. | 
 |     """ | 
 |  | 
 |     interface_name = interface.id | 
 |  | 
 |     # Actual saving | 
 |     file_path = self._FilePath(interface_name) | 
 |     _logger.debug('writing %s' % file_path) | 
 |  | 
 |     dir_name = os.path.dirname(file_path) | 
 |     if not os.path.exists(dir_name): | 
 |       _logger.debug('creating directory %s' % dir_name) | 
 |       os.mkdir(dir_name) | 
 |  | 
 |     # Render the IDLInterface object into text. | 
 |     text = idlrenderer.render(interface) | 
 |  | 
 |     f = open(file_path, 'w') | 
 |     f.write(text) | 
 |     f.close() | 
 |  | 
 |   def HasInterface(self, interface_name): | 
 |     """Returns True if the interface is in memory""" | 
 |     return interface_name in self._all_interfaces | 
 |  | 
 |   def GetInterface(self, interface_name): | 
 |     """Returns an IDLInterface corresponding to the interface_name | 
 |     from memory. | 
 |  | 
 |     Args: | 
 |       interface_name -- the name of the interface. | 
 |     """ | 
 |     if interface_name not in self._all_interfaces: | 
 |       raise RuntimeError('Interface %s is not loaded' % interface_name) | 
 |     return self._all_interfaces[interface_name] | 
 |  | 
 |   def AddInterface(self, interface): | 
 |     """Returns an IDLInterface corresponding to the interface_name | 
 |     from memory. | 
 |  | 
 |     Args: | 
 |       interface -- the name of the interface. | 
 |     """ | 
 |     interface_name = interface.id | 
 |     if interface_name in self._all_interfaces: | 
 |       raise RuntimeError('Interface %s already exists' % interface_name) | 
 |     self._all_interfaces[interface_name] = interface | 
 |  | 
 |   def GetInterfaces(self): | 
 |     """Returns a list of all loaded interfaces.""" | 
 |     res = [] | 
 |     for _, interface in sorted(self._all_interfaces.items()): | 
 |       res.append(interface) | 
 |     return res | 
 |  | 
 |   def DeleteInterface(self, interface_name): | 
 |     """Deletes an interface from the database. File is deleted when | 
 |     Save() is called. | 
 |  | 
 |     Args: | 
 |       interface_name -- the name of the interface. | 
 |     """ | 
 |     if interface_name not in self._all_interfaces: | 
 |       raise RuntimeError('Interface %s not found' % interface_name) | 
 |     self._interfaces_to_delete.append(interface_name) | 
 |     del self._all_interfaces[interface_name] | 
 |  | 
 |   def _DeleteInterfaceFile(self, interface_name): | 
 |     """Actual file deletion""" | 
 |     file_path = self._FilePath(interface_name) | 
 |     if os.path.exists(file_path): | 
 |       _logger.debug('deleting %s' % file_path) | 
 |       os.remove(file_path) |