Launchpad automatic translations update.
[odoo/odoo.git] / openerp / modules / graph.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #    Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU Affero General Public License as
10 #    published by the Free Software Foundation, either version 3 of the
11 #    License, or (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU Affero General Public License for more details.
17 #
18 #    You should have received a copy of the GNU Affero General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 """ Modules dependency graph. """
24
25 import os, sys, imp
26 from os.path import join as opj
27 import itertools
28 import zipimport
29
30 import openerp
31
32 import openerp.osv as osv
33 import openerp.tools as tools
34 import openerp.tools.osutil as osutil
35 from openerp.tools.safe_eval import safe_eval as eval
36 from openerp.tools.translate import _
37
38 import zipfile
39 import openerp.release as release
40
41 import re
42 import base64
43 from zipfile import PyZipFile, ZIP_DEFLATED
44 from cStringIO import StringIO
45
46 import logging
47
48 _logger = logging.getLogger(__name__)
49
50 class Graph(dict):
51     """ Modules dependency graph.
52
53     The graph is a mapping from module name to Nodes.
54
55     """
56
57     def add_node(self, name, info):
58         max_depth, father = 0, None
59         for n in [Node(x, self, None) for x in info['depends']]:
60             if n.depth >= max_depth:
61                 father = n
62                 max_depth = n.depth
63         if father:
64             return father.add_child(name, info)
65         else:
66             return Node(name, self, info)
67
68     def update_from_db(self, cr):
69         if not len(self):
70             return
71         # update the graph with values from the database (if exist)
72         ## First, we set the default values for each package in graph
73         additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None})
74         ## Then we get the values from the database
75         cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
76                    '  FROM ir_module_module'
77                    ' WHERE name IN %s',(tuple(additional_data),)
78                    )
79
80         ## and we update the default values with values from the database
81         additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()]))
82
83         for package in self.values():
84             for k, v in additional_data[package.name].items():
85                 setattr(package, k, v)
86
87     def add_module(self, cr, module, force=None):
88         self.add_modules(cr, [module], force)
89
90     def add_modules(self, cr, module_list, force=None):
91         if force is None:
92             force = []
93         packages = []
94         len_graph = len(self)
95         for module in module_list:
96             # This will raise an exception if no/unreadable descriptor file.
97             # NOTE The call to load_information_from_description_file is already
98             # done by db.initialize, so it is possible to not do it again here.
99             info = openerp.modules.module.load_information_from_description_file(module)
100             if info and info['installable']:
101                 packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version
102             else:
103                 _logger.warning('module %s: not installable, skipped', module)
104
105         dependencies = dict([(p, info['depends']) for p, info in packages])
106         current, later = set([p for p, info in packages]), set()
107
108         while packages and current > later:
109             package, info = packages[0]
110             deps = info['depends']
111
112             # if all dependencies of 'package' are already in the graph, add 'package' in the graph
113             if reduce(lambda x, y: x and y in self, deps, True):
114                 if not package in current:
115                     packages.pop(0)
116                     continue
117                 later.clear()
118                 current.remove(package)
119                 node = self.add_node(package, info)
120                 node.data = info
121                 for kind in ('init', 'demo', 'update'):
122                     if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
123                         setattr(node, kind, True)
124             else:
125                 later.add(package)
126                 packages.append((package, info))
127             packages.pop(0)
128
129         self.update_from_db(cr)
130
131         for package in later:
132             unmet_deps = filter(lambda p: p not in self, dependencies[package])
133             _logger.error('module %s: Unmet dependencies: %s', package, ', '.join(unmet_deps))
134
135         result = len(self) - len_graph
136         if result != len(module_list):
137             _logger.warning('Some modules were not loaded.')
138         return result
139
140
141     def __iter__(self):
142         level = 0
143         done = set(self.keys())
144         while done:
145             level_modules = sorted((name, module) for name, module in self.items() if module.depth==level)
146             for name, module in level_modules:
147                 done.remove(name)
148                 yield module
149             level += 1
150
151
152 class Singleton(object):
153     def __new__(cls, name, graph, info):
154         if name in graph:
155             inst = graph[name]
156         else:
157             inst = object.__new__(cls)
158             inst.name = name
159             inst.info = info
160             graph[name] = inst
161         return inst
162
163
164 class Node(Singleton):
165     """ One module in the modules dependency graph.
166
167     Node acts as a per-module singleton. A node is constructed via
168     Graph.add_module() or Graph.add_modules(). Some of its fields are from
169     ir_module_module (setted by Graph.update_from_db()).
170
171     """
172
173     def __init__(self, name, graph, info):
174         self.graph = graph
175         if not hasattr(self, 'children'):
176             self.children = []
177         if not hasattr(self, 'depth'):
178             self.depth = 0
179
180     def add_child(self, name, info):
181         node = Node(name, self.graph, info)
182         node.depth = self.depth + 1
183         if node not in self.children:
184             self.children.append(node)
185         for attr in ('init', 'update', 'demo'):
186             if hasattr(self, attr):
187                 setattr(node, attr, True)
188         self.children.sort(lambda x, y: cmp(x.name, y.name))
189         return node
190
191     def __setattr__(self, name, value):
192         super(Singleton, self).__setattr__(name, value)
193         if name in ('init', 'update', 'demo'):
194             tools.config[name][self.name] = 1
195             for child in self.children:
196                 setattr(child, name, value)
197         if name == 'depth':
198             for child in self.children:
199                 setattr(child, name, value + 1)
200
201     def __iter__(self):
202         return itertools.chain(iter(self.children), *map(iter, self.children))
203
204     def __str__(self):
205         return self._pprint()
206
207     def _pprint(self, depth=0):
208         s = '%s\n' % self.name
209         for c in self.children:
210             s += '%s`-> %s' % ('   ' * depth, c._pprint(depth+1))
211         return s
212
213
214 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: