[IMP] [FIX] website_blog: better follow mechanism. Now people following
[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-2014 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 d in info['depends']:
60             n = self.get(d) or Node(d, self, None)  # lazy creation, do not use default value for get()
61             if n.depth >= max_depth:
62                 father = n
63                 max_depth = n.depth
64         if father:
65             return father.add_child(name, info)
66         else:
67             return Node(name, self, info)
68
69     def update_from_db(self, cr):
70         if not len(self):
71             return
72         # update the graph with values from the database (if exist)
73         ## First, we set the default values for each package in graph
74         additional_data = dict((key, {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None}) for key in self.keys())
75         ## Then we get the values from the database
76         cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
77                    '  FROM ir_module_module'
78                    ' WHERE name IN %s',(tuple(additional_data),)
79                    )
80
81         ## and we update the default values with values from the database
82         additional_data.update((x['name'], x) for x in cr.dictfetchall())
83
84         for package in self.values():
85             for k, v in additional_data[package.name].items():
86                 setattr(package, k, v)
87
88     def add_module(self, cr, module, force=None):
89         self.add_modules(cr, [module], force)
90
91     def add_modules(self, cr, module_list, force=None):
92         if force is None:
93             force = []
94         packages = []
95         len_graph = len(self)
96         for module in module_list:
97             # This will raise an exception if no/unreadable descriptor file.
98             # NOTE The call to load_information_from_description_file is already
99             # done by db.initialize, so it is possible to not do it again here.
100             info = openerp.modules.module.load_information_from_description_file(module)
101             if info and info['installable']:
102                 packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version
103             else:
104                 _logger.warning('module %s: not installable, skipped', module)
105
106         dependencies = dict([(p, info['depends']) for p, info in packages])
107         current, later = set([p for p, info in packages]), set()
108
109         while packages and current > later:
110             package, info = packages[0]
111             deps = info['depends']
112
113             # if all dependencies of 'package' are already in the graph, add 'package' in the graph
114             if reduce(lambda x, y: x and y in self, deps, True):
115                 if not package in current:
116                     packages.pop(0)
117                     continue
118                 later.clear()
119                 current.remove(package)
120                 node = self.add_node(package, 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     def __str__(self):
152         return '\n'.join(str(n) for n in self if n.depth == 0)
153
154 class Node(object):
155     """ One module in the modules dependency graph.
156
157     Node acts as a per-module singleton. A node is constructed via
158     Graph.add_module() or Graph.add_modules(). Some of its fields are from
159     ir_module_module (setted by Graph.update_from_db()).
160
161     """
162     def __new__(cls, name, graph, info):
163         if name in graph:
164             inst = graph[name]
165         else:
166             inst = object.__new__(cls)
167             graph[name] = inst
168         return inst
169
170     def __init__(self, name, graph, info):
171         self.name = name
172         self.graph = graph
173         self.info = info or getattr(self, 'info', {})
174         if not hasattr(self, 'children'):
175             self.children = []
176         if not hasattr(self, 'depth'):
177             self.depth = 0
178
179     @property
180     def data(self):
181         return self.info
182
183     def add_child(self, name, info):
184         node = Node(name, self.graph, info)
185         node.depth = self.depth + 1
186         if node not in self.children:
187             self.children.append(node)
188         for attr in ('init', 'update', 'demo'):
189             if hasattr(self, attr):
190                 setattr(node, attr, True)
191         self.children.sort(lambda x, y: cmp(x.name, y.name))
192         return node
193
194     def __setattr__(self, name, value):
195         super(Node, self).__setattr__(name, value)
196         if name in ('init', 'update', 'demo'):
197             tools.config[name][self.name] = 1
198             for child in self.children:
199                 setattr(child, name, value)
200         if name == 'depth':
201             for child in self.children:
202                 setattr(child, name, value + 1)
203
204     def __iter__(self):
205         return itertools.chain(iter(self.children), *map(iter, self.children))
206
207     def __str__(self):
208         return self._pprint()
209
210     def _pprint(self, depth=0):
211         s = '%s\n' % self.name
212         for c in self.children:
213             s += '%s`-> %s' % ('   ' * depth, c._pprint(depth+1))
214         return s
215
216
217 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: