############################################################################## # # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) # # $Id$ # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential # consequences resulting from its eventual inadequacies and bugs # End users who are looking for a ready-to-use solution with commercial # garantees and support are strongly adviced to contact a Free Software # Service Company # # This program is Free Software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ############################################################################## import os, sys, imp import itertools from sets import Set import osv import tools import pooler import netsvc from osv import fields import zipfile logger = netsvc.Logger() opj = os.path.join _ad = os.path.abspath(opj(tools.config['root_path'], 'addons')) # default addons path (base) ad = os.path.abspath(tools.config['addons_path']) # alternate addons path sys.path.insert(1, _ad) if ad != _ad: sys.path.insert(1, ad) class Graph(dict): def addNode(self, name, deps): max_depth, father = 0, None for n in [Node(x, self) for x in deps]: if n.depth >= max_depth: father = n max_depth = n.depth if father: father.addChild(name) else: Node(name, self) def __iter__(self): level = 0 done = Set(self.keys()) while done: level_modules = [(name, module) for name, module in self.items() if module.depth==level] for name, module in level_modules: done.remove(name) yield module level += 1 class Singleton(object): def __new__(cls, name, graph): if name in graph: inst = graph[name] else: inst = object.__new__(cls) inst.name = name graph[name] = inst return inst class Node(Singleton): def __init__(self, name, graph): self.graph = graph if not hasattr(self, 'childs'): self.childs = [] if not hasattr(self, 'depth'): self.depth = 0 def addChild(self, name): node = Node(name, self.graph) node.depth = self.depth + 1 if node not in self.childs: self.childs.append(node) for attr in ('init', 'update', 'demo'): if hasattr(self, attr): setattr(node, attr, True) self.childs.sort(lambda x,y: cmp(x.name, y.name)) def hasChild(self, name): return Node(name, self.graph) in self.childs or \ bool([c for c in self.childs if c.hasChild(name)]) def __setattr__(self, name, value): super(Singleton, self).__setattr__(name, value) if name in ('init', 'update', 'demo'): tools.config[name][self.name] = 1 for child in self.childs: setattr(child, name, value) if name == 'depth': for child in self.childs: setattr(child, name, value + 1) def __iter__(self): return itertools.chain(iter(self.childs), *map(iter, self.childs)) def __str__(self): return self._pprint() def _pprint(self, depth=0): s = '%s\n' % self.name for c in self.childs: s += '%s`-> %s' % (' ' * depth, c._pprint(depth+1)) return s def get_module_path(module): """Return the path of the given module. """ if os.path.exists(opj(ad, module)) or os.path.exists(opj(ad, '%s.zip' % module)): return opj(ad, module) if os.path.exists(opj(_ad, module)) or os.path.exists(opj(_ad, '%s.zip' % module)): return opj(_ad, module) raise IOError, 'Module not found : %s' % module def get_module_resource(module, *args): """Return the full path of a resource of the given module. @param module: the module @param args: the resource path components @return: absolute path to the resource """ return opj(get_module_path(module), *args) def get_modules(): """Returns the list of module names """ module_list = os.listdir(ad) module_names = [os.path.basename(m) for m in module_list] module_list += [m for m in os.listdir(_ad) if m not in module_names] return module_list def create_graph(module_list, force=None): if not force: force=[] graph = Graph() packages = [] for module in module_list: if module[-4:]=='.zip': module = module[:-4] mod_path = get_module_path(module) terp_file = get_module_resource(module, '__terp__.py') if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path): try: info = eval(tools.file_open(terp_file).read()) except: logger.notifyChannel('init', netsvc.LOG_ERROR, 'addon:%s:eval file %s' % (module, terp_file)) raise if info.get('installable', True): packages.append((module, info.get('depends', []), info)) current,later = Set([p for p, dep, data in packages]), Set() while packages and current > later: package, deps, datas = packages[0] # if all dependencies of 'package' are already in the graph, add 'package' in the graph if reduce(lambda x,y: x and y in graph, deps, True): if not package in current: packages.pop(0) continue later.clear() current.remove(package) graph.addNode(package, deps) node = Node(package, graph) node.datas = datas for kind in ('init', 'demo', 'update'): if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force: setattr(node, kind, True) else: later.add(package) packages.append((package, deps, datas)) packages.pop(0) for package in later: logger.notifyChannel('init', netsvc.LOG_ERROR, 'addon:%s:Unmet dependency' % package) return graph def init_module_objects(cr, module_name, obj_list): pool = pooler.get_pool(cr.dbname) logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:creating or updating database tables' % module_name) for obj in obj_list: if hasattr(obj, 'init'): obj.init(cr) obj._auto_init(cr, {'module': module_name}) cr.commit() def load_module_graph(cr, graph, status=None, **kwargs): # **kwargs is passed directly to convert_xml_import if not status: status={} status = status.copy() package_todo = [] statusi = 0 for package in graph: status['progress'] = (float(statusi)+0.1)/len(graph) m = package.name logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s' % m) sys.stdout.flush() pool = pooler.get_pool(cr.dbname) modules = pool.instanciate(m, cr) cr.execute('select state, demo from ir_module_module where name=%s', (m,)) (package_state, package_demo) = (cr.rowcount and cr.fetchone()) or ('uninstalled', False) idref = {} status['progress'] = (float(statusi)+0.4)/len(graph) if hasattr(package, 'init') or hasattr(package, 'update') or package_state in ('to install', 'to upgrade'): init_module_objects(cr, m, modules) for kind in ('init', 'update'): for filename in package.datas.get('%s_xml' % kind, []): mode = 'update' if hasattr(package, 'init') or package_state=='to install': mode = 'init' logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:loading %s' % (m, filename)) name, ext = os.path.splitext(filename) if ext == '.csv': tools.convert_csv_import(cr, m, os.path.basename(filename), tools.file_open(opj(m, filename)).read(), idref, mode=mode) elif ext == '.sql': queries = tools.file_open(opj(m, filename)).read().split(';') for query in queries: new_query = ' '.join(query.split()) if new_query: cr.execute(new_query) else: tools.convert_xml_import(cr, m, tools.file_open(opj(m, filename)), idref, mode=mode, **kwargs) if hasattr(package, 'demo') or (package_demo and package_state != 'installed'): status['progress'] = (float(statusi)+0.75)/len(graph) for xml in package.datas.get('demo_xml', []): name, ext = os.path.splitext(xml) logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:loading %s' % (m, xml)) if ext == '.csv': tools.convert_csv_import(cr, m, os.path.basename(xml), tools.file_open(opj(m, xml)).read(), idref, noupdate=True) else: tools.convert_xml_import(cr, m, tools.file_open(opj(m, xml)), idref, noupdate=True, **kwargs) cr.execute('update ir_module_module set demo=%s where name=%s', (True, package.name)) package_todo.append(package.name) cr.execute("update ir_module_module set state='installed' where state in ('to upgrade', 'to install') and name=%s", (package.name,)) cr.commit() statusi+=1 pool = pooler.get_pool(cr.dbname) cr.execute('select * from ir_model where state=%s', ('manual',)) for model in cr.dictfetchall(): pool.get('ir.model').instanciate(cr, 1, model['model'], {}) pool.get('ir.model.data')._process_end(cr, 1, package_todo) cr.commit() def register_classes(): module_list = get_modules() for package in create_graph(module_list): m = package.name logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:registering classes' % m) sys.stdout.flush() mod_path = get_module_path(m) if not os.path.isfile(mod_path+'.zip'): # XXX must restrict to only addons paths imp.load_module(m, *imp.find_module(m)) else: import zipimport try: zimp = zipimport.zipimporter(mod_path+'.zip') zimp.load_module(m) except zipimport.ZipImportError: logger.notifyChannel('init', netsvc.LOG_ERROR, 'Couldn\'t find module %s' % m) def load_modules(db, force_demo=False, status=None, update_module=False): if not status: status={} cr = db.cursor() force = [] if force_demo: force.append('demo') if update_module: cr.execute("select name from ir_module_module where state in ('installed', 'to install', 'to upgrade','to remove')") else: cr.execute("select name from ir_module_module where state in ('installed', 'to upgrade', 'to remove')") module_list = [name for (name,) in cr.fetchall()] graph = create_graph(module_list, force) report = tools.assertion_report() load_module_graph(cr, graph, status, report=report) if report.get_report(): logger.notifyChannel('init', netsvc.LOG_INFO, 'assert:%s' % report) for kind in ('init', 'demo', 'update'): tools.config[kind]={} cr.commit() if update_module: cr.execute("select id,name from ir_module_module where state in ('to remove')") for mod_id, mod_name in cr.fetchall(): pool = pooler.get_pool(cr.dbname) cr.execute('select model,res_id from ir_model_data where not noupdate and module=%s order by id desc', (mod_name,)) for rmod,rid in cr.fetchall(): # # TO BE Improved: # I can not use the class_pool has _table could be defined in __init__ # and I can not use the pool has the module could not be loaded in the pool # uid = 1 pool.get(rmod).unlink(cr, uid, [rid]) cr.commit() # # TODO: remove menu without actions of childs # cr.execute('''delete from ir_ui_menu where (id not in (select parent_id from ir_ui_menu where parent_id is not null)) and (id not in (select res_id from ir_values where model='ir.ui.menu')) and (id not in (select res_id from ir_model_data where model='ir.ui.menu'))''') cr.execute("update ir_module_module set state=%s where state in ('to remove')", ('uninstalled', )) cr.commit() pooler.restart_pool(cr.dbname) cr.close()