Merge lp:~openerp-dev/openobject-server/trunk-trunk-datadir-chs into lp:openobject-server
- trunk-trunk-datadir-chs
- Merge into trunk
Proposed by
Christophe Simonis (OpenERP)
Status: | Merged |
---|---|
Merged at revision: | 5103 |
Proposed branch: | lp:~openerp-dev/openobject-server/trunk-trunk-datadir-chs |
Merge into: | lp:openobject-server |
Diff against target: |
1115 lines (+642/-168) 13 files modified
openerp/addons/base/ir/ir_attachment.py (+41/-29) openerp/addons/base/module/wizard/base_module_import.py (+0/-2) openerp/addons/base/tests/test_ir_attachment.py (+68/-65) openerp/cli/server.py (+1/-1) openerp/http.py (+1/-22) openerp/modules/module.py (+9/-7) openerp/release.py (+2/-2) openerp/service/db.py (+1/-6) openerp/service/server.py (+2/-3) openerp/tools/__init__.py (+1/-0) openerp/tools/appdirs.py (+477/-0) openerp/tools/config.py (+29/-4) openerp/tools/translate.py (+10/-27) |
To merge this branch: | bzr merge lp:~openerp-dev/openobject-server/trunk-trunk-datadir-chs |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenERP Core Team | Pending | ||
Review via email: mp+202059@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
- 5046. By Christophe Simonis (OpenERP)
-
[FIX] attachments: filestore use dbname instead of dbuuid
- 5047. By Antony Lesuisse (OpenERP)
-
[MERGE] trunk
- 5048. By Antony Lesuisse (OpenERP)
-
[FIX] move appsdirs to tools
- 5049. By Christophe Simonis (OpenERP)
-
merge upstream
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'openerp/addons/base/ir/ir_attachment.py' |
2 | --- openerp/addons/base/ir/ir_attachment.py 2014-01-16 09:17:16 +0000 |
3 | +++ openerp/addons/base/ir/ir_attachment.py 2014-02-27 16:58:29 +0000 |
4 | @@ -64,19 +64,37 @@ |
5 | data[attachment.id] = False |
6 | return data |
7 | |
8 | + def _storage(self, cr, uid, context=None): |
9 | + return self.pool['ir.config_parameter'].get_param(cr, SUPERUSER_ID, 'ir_attachment.location', 'file') |
10 | + |
11 | + @tools.ormcache() |
12 | + def _filestore(self, cr, uid, context=None): |
13 | + return os.path.join(tools.config['data_dir'], 'filestore', cr.dbname) |
14 | + |
15 | # 'data' field implementation |
16 | def _full_path(self, cr, uid, location, path): |
17 | - # location = 'file:filestore' |
18 | - assert location.startswith('file:'), "Unhandled filestore location %s" % location |
19 | - location = location[5:] |
20 | - |
21 | - # sanitize location name and path |
22 | - location = re.sub('[.]','',location) |
23 | - location = location.strip('/\\') |
24 | - |
25 | - path = re.sub('[.]','',path) |
26 | + # sanitize ath |
27 | + path = re.sub('[.]', '', path) |
28 | path = path.strip('/\\') |
29 | - return os.path.join(tools.config['root_path'], location, cr.dbname, path) |
30 | + return os.path.join(self._filestore(cr, uid), path) |
31 | + |
32 | + def _get_path(self, cr, uid, location, bin_data): |
33 | + sha = hashlib.sha1(bin_data).hexdigest() |
34 | + |
35 | + # retro compatibility |
36 | + fname = sha[:3] + '/' + sha |
37 | + full_path = self._full_path(cr, uid, location, fname) |
38 | + if os.path.isfile(full_path): |
39 | + return fname, full_path # keep existing path |
40 | + |
41 | + # scatter files across 256 dirs |
42 | + # we use '/' in the db (even on windows) |
43 | + fname = sha[:2] + '/' + sha |
44 | + full_path = self._full_path(cr, uid, location, fname) |
45 | + dirname = os.path.dirname(full_path) |
46 | + if not os.path.isdir(dirname): |
47 | + os.makedirs(dirname) |
48 | + return fname, full_path |
49 | |
50 | def _file_read(self, cr, uid, location, fname, bin_size=False): |
51 | full_path = self._full_path(cr, uid, location, fname) |
52 | @@ -92,18 +110,13 @@ |
53 | |
54 | def _file_write(self, cr, uid, location, value): |
55 | bin_value = value.decode('base64') |
56 | - fname = hashlib.sha1(bin_value).hexdigest() |
57 | - # scatter files across 1024 dirs |
58 | - # we use '/' in the db (even on windows) |
59 | - fname = fname[:3] + '/' + fname |
60 | - full_path = self._full_path(cr, uid, location, fname) |
61 | - try: |
62 | - dirname = os.path.dirname(full_path) |
63 | - if not os.path.isdir(dirname): |
64 | - os.makedirs(dirname) |
65 | - open(full_path,'wb').write(bin_value) |
66 | - except IOError: |
67 | - _logger.error("_file_write writing %s",full_path) |
68 | + fname, full_path = self._get_path(cr, uid, location, bin_value) |
69 | + if not os.path.exists(full_path): |
70 | + try: |
71 | + with open(full_path, 'wb') as fp: |
72 | + fp.write(bin_value) |
73 | + except IOError: |
74 | + _logger.error("_file_write writing %s", full_path) |
75 | return fname |
76 | |
77 | def _file_delete(self, cr, uid, location, fname): |
78 | @@ -122,10 +135,10 @@ |
79 | if context is None: |
80 | context = {} |
81 | result = {} |
82 | - location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location') |
83 | + location = self._storage(cr, uid, context) |
84 | bin_size = context.get('bin_size') |
85 | for attach in self.browse(cr, uid, ids, context=context): |
86 | - if location and attach.store_fname: |
87 | + if location != 'db' and attach.store_fname: |
88 | result[attach.id] = self._file_read(cr, uid, location, attach.store_fname, bin_size) |
89 | else: |
90 | result[attach.id] = attach.db_datas |
91 | @@ -137,9 +150,9 @@ |
92 | return True |
93 | if context is None: |
94 | context = {} |
95 | - location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location') |
96 | + location = self._storage(cr, uid, context) |
97 | file_size = len(value.decode('base64')) |
98 | - if location: |
99 | + if location != 'db': |
100 | attach = self.browse(cr, uid, id, context=context) |
101 | if attach.store_fname: |
102 | self._file_delete(cr, uid, location, attach.store_fname) |
103 | @@ -285,8 +298,8 @@ |
104 | if isinstance(ids, (int, long)): |
105 | ids = [ids] |
106 | self.check(cr, uid, ids, 'unlink', context=context) |
107 | - location = self.pool.get('ir.config_parameter').get_param(cr, uid, 'ir_attachment.location') |
108 | - if location: |
109 | + location = self._storage(cr, uid, context) |
110 | + if location != 'db': |
111 | for attach in self.browse(cr, uid, ids, context=context): |
112 | if attach.store_fname: |
113 | self._file_delete(cr, uid, location, attach.store_fname) |
114 | @@ -303,4 +316,3 @@ |
115 | cr, uid, 'base', 'action_attachment', context=context) |
116 | |
117 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
118 | - |
119 | |
120 | === modified file 'openerp/addons/base/module/wizard/base_module_import.py' |
121 | --- openerp/addons/base/module/wizard/base_module_import.py 2013-10-18 15:48:05 +0000 |
122 | +++ openerp/addons/base/module/wizard/base_module_import.py 2014-02-27 16:58:29 +0000 |
123 | @@ -28,8 +28,6 @@ |
124 | from openerp.osv import osv, fields |
125 | from openerp.tools.translate import _ |
126 | |
127 | -ADDONS_PATH = tools.config['addons_path'].split(",")[-1] |
128 | - |
129 | class base_module_import(osv.osv_memory): |
130 | """ Import Module """ |
131 | |
132 | |
133 | === modified file 'openerp/addons/base/tests/test_ir_attachment.py' |
134 | --- openerp/addons/base/tests/test_ir_attachment.py 2012-12-16 19:03:17 +0000 |
135 | +++ openerp/addons/base/tests/test_ir_attachment.py 2014-02-27 16:58:29 +0000 |
136 | @@ -1,89 +1,92 @@ |
137 | import hashlib |
138 | import os |
139 | |
140 | -import unittest2 |
141 | - |
142 | import openerp |
143 | import openerp.tests.common |
144 | |
145 | +HASH_SPLIT = 2 # FIXME: testing implementations detail is not a good idea |
146 | + |
147 | class test_ir_attachment(openerp.tests.common.TransactionCase): |
148 | - |
149 | - def test_00_attachment_flow(self): |
150 | + def setUp(self): |
151 | + super(test_ir_attachment, self).setUp() |
152 | registry, cr, uid = self.registry, self.cr, self.uid |
153 | - root_path = openerp.tools.config['root_path'] |
154 | - ira = registry('ir.attachment') |
155 | + self.ira = registry('ir.attachment') |
156 | + self.filestore = self.ira._filestore(cr, uid) |
157 | |
158 | # Blob1 |
159 | - blob1 = 'blob1' |
160 | - blob1_b64 = blob1.encode('base64') |
161 | - blob1_hash = hashlib.sha1(blob1).hexdigest() |
162 | - blob1_fname = blob1_hash[:3] + '/' + blob1_hash |
163 | + self.blob1 = 'blob1' |
164 | + self.blob1_b64 = self.blob1.encode('base64') |
165 | + blob1_hash = hashlib.sha1(self.blob1).hexdigest() |
166 | + self.blob1_fname = blob1_hash[:HASH_SPLIT] + '/' + blob1_hash |
167 | |
168 | # Blob2 |
169 | blob2 = 'blob2' |
170 | - blob2_b64 = blob2.encode('base64') |
171 | - blob2_hash = hashlib.sha1(blob2).hexdigest() |
172 | - blob2_fname = blob2_hash[:3] + '/' + blob2_hash |
173 | + self.blob2_b64 = blob2.encode('base64') |
174 | + |
175 | + def test_01_store_in_db(self): |
176 | + registry, cr, uid = self.registry, self.cr, self.uid |
177 | + |
178 | + # force storing in database |
179 | + registry('ir.config_parameter').set_param(cr, uid, 'ir_attachment.location', 'db') |
180 | |
181 | # 'ir_attachment.location' is undefined test database storage |
182 | - a1 = ira.create(cr, uid, {'name': 'a1', 'datas': blob1_b64}) |
183 | - a1_read = ira.read(cr, uid, [a1], ['datas']) |
184 | - self.assertEqual(a1_read[0]['datas'], blob1_b64) |
185 | - |
186 | - cr.execute("select id,db_datas from ir_attachment where id = %s", (a1,) ) |
187 | - a1_db_datas = str(cr.fetchall()[0][1]) |
188 | - self.assertEqual(a1_db_datas, blob1_b64) |
189 | - |
190 | - # define a location for filestore |
191 | - registry('ir.config_parameter').set_param(cr, uid, 'ir_attachment.location', 'file:///filestore') |
192 | - |
193 | - # Test file storage |
194 | - a2 = ira.create(cr, uid, {'name': 'a2', 'datas': blob1_b64}) |
195 | - a2_read = ira.read(cr, uid, [a2], ['datas']) |
196 | - self.assertEqual(a2_read[0]['datas'], blob1_b64) |
197 | - |
198 | - cr.execute("select id,store_fname from ir_attachment where id = %s", (a2,) ) |
199 | - a2_store_fname = cr.fetchall()[0][1] |
200 | - self.assertEqual(a2_store_fname, blob1_fname) |
201 | - |
202 | - a2_fn = os.path.join(root_path, 'filestore', cr.dbname, blob1_hash[:3], blob1_hash) |
203 | - fc = file(a2_fn).read() |
204 | - self.assertEqual(fc, blob1) |
205 | - |
206 | - # create a3 with same blob |
207 | - a3 = ira.create(cr, uid, {'name': 'a3', 'datas': blob1_b64}) |
208 | - a3_read = ira.read(cr, uid, [a3], ['datas']) |
209 | - self.assertEqual(a3_read[0]['datas'], blob1_b64) |
210 | - |
211 | - cr.execute("select id,store_fname from ir_attachment where id = %s", (a3,) ) |
212 | - a3_store_fname = cr.fetchall()[0][1] |
213 | + a1 = self.ira.create(cr, uid, {'name': 'a1', 'datas': self.blob1_b64}) |
214 | + a1_read = self.ira.read(cr, uid, [a1], ['datas']) |
215 | + self.assertEqual(a1_read[0]['datas'], self.blob1_b64) |
216 | + |
217 | + a1_db_datas = self.ira.browse(cr, uid, a1).db_datas |
218 | + self.assertEqual(a1_db_datas, self.blob1_b64) |
219 | + |
220 | + def test_02_store_on_disk(self): |
221 | + registry, cr, uid = self.registry, self.cr, self.uid |
222 | + |
223 | + a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64}) |
224 | + a2_store_fname = self.ira.browse(cr, uid, a2).store_fname |
225 | + |
226 | + self.assertEqual(a2_store_fname, self.blob1_fname) |
227 | + self.assertTrue(os.path.isfile(os.path.join(self.filestore, a2_store_fname))) |
228 | + |
229 | + def test_03_no_duplication(self): |
230 | + registry, cr, uid = self.registry, self.cr, self.uid |
231 | + |
232 | + a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64}) |
233 | + a2_store_fname = self.ira.browse(cr, uid, a2).store_fname |
234 | + |
235 | + a3 = self.ira.create(cr, uid, {'name': 'a3', 'datas': self.blob1_b64}) |
236 | + a3_store_fname = self.ira.browse(cr, uid, a3).store_fname |
237 | + |
238 | self.assertEqual(a3_store_fname, a2_store_fname) |
239 | |
240 | - # create a4 blob2 |
241 | - a4 = ira.create(cr, uid, {'name': 'a4', 'datas': blob2_b64}) |
242 | - a4_read = ira.read(cr, uid, [a4], ['datas']) |
243 | - self.assertEqual(a4_read[0]['datas'], blob2_b64) |
244 | - |
245 | - a4_fn = os.path.join(root_path, 'filestore', cr.dbname, blob2_hash[:3], blob2_hash) |
246 | - self.assertTrue(os.path.isfile(a4_fn)) |
247 | - |
248 | - # delete a3 but file stays |
249 | - ira.unlink(cr, uid, [a3]) |
250 | + def test_04_keep_file(self): |
251 | + registry, cr, uid = self.registry, self.cr, self.uid |
252 | + |
253 | + a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64}) |
254 | + a3 = self.ira.create(cr, uid, {'name': 'a3', 'datas': self.blob1_b64}) |
255 | + |
256 | + a2_store_fname = self.ira.browse(cr, uid, a2).store_fname |
257 | + a2_fn = os.path.join(self.filestore, a2_store_fname) |
258 | + |
259 | + self.ira.unlink(cr, uid, [a3]) |
260 | self.assertTrue(os.path.isfile(a2_fn)) |
261 | |
262 | # delete a2 it is unlinked |
263 | - ira.unlink(cr, uid, [a2]) |
264 | + self.ira.unlink(cr, uid, [a2]) |
265 | self.assertFalse(os.path.isfile(a2_fn)) |
266 | |
267 | - # update a4 blob2 by blob1 |
268 | - ira.write(cr, uid, [a4], {'datas': blob1_b64}) |
269 | - a4_read = ira.read(cr, uid, [a4], ['datas']) |
270 | - self.assertEqual(a4_read[0]['datas'], blob1_b64) |
271 | - |
272 | - # file of a4 disapear and a2 reappear |
273 | - self.assertFalse(os.path.isfile(a4_fn)) |
274 | + def test_05_change_data_change_file(self): |
275 | + registry, cr, uid = self.registry, self.cr, self.uid |
276 | + |
277 | + a2 = self.ira.create(cr, uid, {'name': 'a2', 'datas': self.blob1_b64}) |
278 | + a2_store_fname = self.ira.browse(cr, uid, a2).store_fname |
279 | + a2_fn = os.path.join(self.filestore, a2_store_fname) |
280 | + |
281 | self.assertTrue(os.path.isfile(a2_fn)) |
282 | |
283 | - # everybody applause |
284 | - |
285 | - |
286 | + self.ira.write(cr, uid, [a2], {'datas': self.blob2_b64}) |
287 | + self.assertFalse(os.path.isfile(a2_fn)) |
288 | + |
289 | + new_a2_store_fname = self.ira.browse(cr, uid, a2).store_fname |
290 | + self.assertNotEqual(a2_store_fname, new_a2_store_fname) |
291 | + |
292 | + new_a2_fn = os.path.join(self.filestore, new_a2_store_fname) |
293 | + self.assertTrue(os.path.isfile(new_a2_fn)) |
294 | |
295 | === modified file 'openerp/cli/server.py' |
296 | --- openerp/cli/server.py 2014-02-09 01:46:36 +0000 |
297 | +++ openerp/cli/server.py 2014-02-27 16:58:29 +0000 |
298 | @@ -72,7 +72,7 @@ |
299 | """ |
300 | config = openerp.tools.config |
301 | _logger.info("OpenERP version %s", __version__) |
302 | - for name, value in [('addons paths', config['addons_path']), |
303 | + for name, value in [('addons paths', openerp.modules.module.ad_paths), |
304 | ('database hostname', config['db_host'] or 'localhost'), |
305 | ('database port', config['db_port'] or '5432'), |
306 | ('database user', config['db_user'])]: |
307 | |
308 | === modified file 'openerp/http.py' |
309 | --- openerp/http.py 2014-02-26 16:16:27 +0000 |
310 | +++ openerp/http.py 2014-02-27 16:58:29 +0000 |
311 | @@ -995,33 +995,12 @@ |
312 | start_response(status, new_headers) |
313 | return self.app(environ, start_wrapped) |
314 | |
315 | -def session_path(): |
316 | - try: |
317 | - import pwd |
318 | - username = pwd.getpwuid(os.geteuid()).pw_name |
319 | - except ImportError: |
320 | - try: |
321 | - username = getpass.getuser() |
322 | - except Exception: |
323 | - username = "unknown" |
324 | - path = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username) |
325 | - try: |
326 | - os.mkdir(path, 0700) |
327 | - except OSError as exc: |
328 | - if exc.errno == errno.EEXIST: |
329 | - # directory exists: ensure it has the correct permissions |
330 | - # this will fail if the directory is not owned by the current user |
331 | - os.chmod(path, 0700) |
332 | - else: |
333 | - raise |
334 | - return path |
335 | - |
336 | class Root(object): |
337 | """Root WSGI application for the OpenERP Web Client. |
338 | """ |
339 | def __init__(self): |
340 | # Setup http sessions |
341 | - path = session_path() |
342 | + path = openerp.tools.config.session_dir |
343 | _logger.debug('HTTP sessions stored in: %s', path) |
344 | self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession) |
345 | |
346 | |
347 | === modified file 'openerp/modules/module.py' |
348 | --- openerp/modules/module.py 2014-02-18 10:18:47 +0000 |
349 | +++ openerp/modules/module.py 2014-02-27 16:58:29 +0000 |
350 | @@ -3,7 +3,7 @@ |
351 | # |
352 | # OpenERP, Open Source Management Solution |
353 | # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). |
354 | -# Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>). |
355 | +# Copyright (C) 2010-2014 OpenERP s.a. (<http://openerp.com>). |
356 | # |
357 | # This program is free software: you can redistribute it and/or modify |
358 | # it under the terms of the GNU Affero General Public License as |
359 | @@ -40,9 +40,6 @@ |
360 | _logger = logging.getLogger(__name__) |
361 | _test_logger = logging.getLogger('openerp.tests') |
362 | |
363 | -# addons path ','.joined |
364 | -_ad = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons') # default addons path (base) |
365 | - |
366 | # addons path as a list |
367 | ad_paths = [] |
368 | |
369 | @@ -90,8 +87,13 @@ |
370 | if ad_paths: |
371 | return |
372 | |
373 | - ad_paths = map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(',')) |
374 | - ad_paths.append(os.path.abspath(_ad)) # for get_module_path |
375 | + ad_paths = [tools.config.addons_data_dir] |
376 | + ad_paths += map(lambda m: os.path.abspath(tools.ustr(m.strip())), tools.config['addons_path'].split(',')) |
377 | + |
378 | + # add base module path |
379 | + base_path = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'addons')) |
380 | + ad_paths += [base_path] |
381 | + |
382 | sys.meta_path.append(AddonsImportHook()) |
383 | |
384 | def get_module_path(module, downloaded=False, display_warning=True): |
385 | @@ -108,7 +110,7 @@ |
386 | return opj(adp, module) |
387 | |
388 | if downloaded: |
389 | - return opj(_ad, module) |
390 | + return opj(tools.config.addons_data_dir, module) |
391 | if display_warning: |
392 | _logger.warning('module %s: module not found', module) |
393 | return False |
394 | |
395 | === modified file 'openerp/release.py' |
396 | --- openerp/release.py 2014-02-11 10:53:15 +0000 |
397 | +++ openerp/release.py 2014-02-27 16:58:29 +0000 |
398 | @@ -32,7 +32,7 @@ |
399 | # (6,1,0,'candidate',2) < (6,1,0,'final',0) < (6,1,2,'final',0) |
400 | version_info = (8, 0, 0, ALPHA, 1) |
401 | version = '.'.join(map(str, version_info[:2])) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '') |
402 | -serie = major_version = '.'.join(map(str, version_info[:2])) |
403 | +series = serie = major_version = '.'.join(map(str, version_info[:2])) |
404 | |
405 | description = 'OpenERP Server' |
406 | long_desc = '''OpenERP is a complete ERP and CRM. The main features are accounting (analytic |
407 | @@ -50,6 +50,6 @@ |
408 | author_email = 'info@openerp.com' |
409 | license = 'AGPL-3' |
410 | |
411 | -nt_service_name = "openerp-server-" + serie |
412 | +nt_service_name = "openerp-server-" + series |
413 | |
414 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
415 | |
416 | === modified file 'openerp/service/db.py' |
417 | --- openerp/service/db.py 2014-02-20 13:27:00 +0000 |
418 | +++ openerp/service/db.py 2014-02-27 16:58:29 +0000 |
419 | @@ -276,15 +276,10 @@ |
420 | cr.autocommit(True) # avoid transaction block |
421 | try: |
422 | cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name)) |
423 | + _logger.info('RENAME DB: %s -> %s', old_name, new_name) |
424 | except Exception, e: |
425 | _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e) |
426 | raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e)) |
427 | - else: |
428 | - fs = os.path.join(openerp.tools.config['root_path'], 'filestore') |
429 | - if os.path.exists(os.path.join(fs, old_name)): |
430 | - os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name)) |
431 | - |
432 | - _logger.info('RENAME DB: %s -> %s', old_name, new_name) |
433 | return True |
434 | |
435 | def exp_db_exist(db_name): |
436 | |
437 | === modified file 'openerp/service/server.py' |
438 | --- openerp/service/server.py 2014-02-21 23:10:10 +0000 |
439 | +++ openerp/service/server.py 2014-02-27 16:58:29 +0000 |
440 | @@ -108,15 +108,14 @@ |
441 | self.handler = EventHandler(self) |
442 | self.notifier = pyinotify.Notifier(self.wm, self.handler, timeout=0) |
443 | mask = pyinotify.IN_MODIFY | pyinotify.IN_CREATE # IN_MOVED_FROM, IN_MOVED_TO ? |
444 | - for path in openerp.tools.config.options["addons_path"].split(','): |
445 | + for path in openerp.modules.modules.ad_paths: |
446 | _logger.info('Watching addons folder %s', path) |
447 | self.wm.add_watch(path, mask, rec=True) |
448 | |
449 | def process_data(self, files): |
450 | xml_files = [i for i in files if i.endswith('.xml')] |
451 | - addons_path = openerp.tools.config.options["addons_path"].split(',') |
452 | for i in xml_files: |
453 | - for path in addons_path: |
454 | + for path in openerp.modules.modules.ad_paths: |
455 | if i.startswith(path): |
456 | # find out wich addons path the file belongs to |
457 | # and extract it's module name |
458 | |
459 | === modified file 'openerp/tools/__init__.py' |
460 | --- openerp/tools/__init__.py 2013-02-12 14:24:10 +0000 |
461 | +++ openerp/tools/__init__.py 2014-02-27 16:58:29 +0000 |
462 | @@ -21,6 +21,7 @@ |
463 | |
464 | import copy |
465 | import win32 |
466 | +import appdirs |
467 | from config import config |
468 | from misc import * |
469 | from convert import * |
470 | |
471 | === added file 'openerp/tools/appdirs.py' |
472 | --- openerp/tools/appdirs.py 1970-01-01 00:00:00 +0000 |
473 | +++ openerp/tools/appdirs.py 2014-02-27 16:58:29 +0000 |
474 | @@ -0,0 +1,477 @@ |
475 | +#!/usr/bin/env python |
476 | +# -*- coding: utf-8 -*- |
477 | +# Copyright (c) 2005-2010 ActiveState Software Inc. |
478 | +# Copyright (c) 2013 Eddy Petrișor |
479 | + |
480 | +"""Utilities for determining application-specific dirs. |
481 | + |
482 | +See <http://github.com/ActiveState/appdirs> for details and usage. |
483 | +""" |
484 | +# Dev Notes: |
485 | +# - MSDN on where to store app data files: |
486 | +# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 |
487 | +# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html |
488 | +# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html |
489 | + |
490 | +__version_info__ = (1, 3, 0) |
491 | +__version__ = '.'.join(map(str, __version_info__)) |
492 | + |
493 | + |
494 | +import sys |
495 | +import os |
496 | + |
497 | +PY3 = sys.version_info[0] == 3 |
498 | + |
499 | +if PY3: |
500 | + unicode = str |
501 | + |
502 | + |
503 | + |
504 | +def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): |
505 | + r"""Return full path to the user-specific data dir for this application. |
506 | + |
507 | + "appname" is the name of application. |
508 | + If None, just the system directory is returned. |
509 | + "appauthor" (only required and used on Windows) is the name of the |
510 | + appauthor or distributing body for this application. Typically |
511 | + it is the owning company name. This falls back to appname. |
512 | + "version" is an optional version path element to append to the |
513 | + path. You might want to use this if you want multiple versions |
514 | + of your app to be able to run independently. If used, this |
515 | + would typically be "<major>.<minor>". |
516 | + Only applied when appname is present. |
517 | + "roaming" (boolean, default False) can be set True to use the Windows |
518 | + roaming appdata directory. That means that for users on a Windows |
519 | + network setup for roaming profiles, this user data will be |
520 | + sync'd on login. See |
521 | + <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> |
522 | + for a discussion of issues. |
523 | + |
524 | + Typical user data directories are: |
525 | + Mac OS X: ~/Library/Application Support/<AppName> |
526 | + Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined |
527 | + Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName> |
528 | + Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName> |
529 | + Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName> |
530 | + Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName> |
531 | + |
532 | + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. |
533 | + That means, by deafult "~/.local/share/<AppName>". |
534 | + """ |
535 | + if sys.platform == "win32": |
536 | + if appauthor is None: |
537 | + appauthor = appname |
538 | + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" |
539 | + path = os.path.normpath(_get_win_folder(const)) |
540 | + if appname: |
541 | + path = os.path.join(path, appauthor, appname) |
542 | + elif sys.platform == 'darwin': |
543 | + path = os.path.expanduser('~/Library/Application Support/') |
544 | + if appname: |
545 | + path = os.path.join(path, appname) |
546 | + else: |
547 | + path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) |
548 | + if appname: |
549 | + path = os.path.join(path, appname) |
550 | + if appname and version: |
551 | + path = os.path.join(path, version) |
552 | + return path |
553 | + |
554 | + |
555 | +def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): |
556 | + """Return full path to the user-shared data dir for this application. |
557 | + |
558 | + "appname" is the name of application. |
559 | + If None, just the system directory is returned. |
560 | + "appauthor" (only required and used on Windows) is the name of the |
561 | + appauthor or distributing body for this application. Typically |
562 | + it is the owning company name. This falls back to appname. |
563 | + "version" is an optional version path element to append to the |
564 | + path. You might want to use this if you want multiple versions |
565 | + of your app to be able to run independently. If used, this |
566 | + would typically be "<major>.<minor>". |
567 | + Only applied when appname is present. |
568 | + "multipath" is an optional parameter only applicable to *nix |
569 | + which indicates that the entire list of data dirs should be |
570 | + returned. By default, the first item from XDG_DATA_DIRS is |
571 | + returned, or '/usr/local/share/<AppName>', |
572 | + if XDG_DATA_DIRS is not set |
573 | + |
574 | + Typical user data directories are: |
575 | + Mac OS X: /Library/Application Support/<AppName> |
576 | + Unix: /usr/local/share/<AppName> or /usr/share/<AppName> |
577 | + Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName> |
578 | + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) |
579 | + Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7. |
580 | + |
581 | + For Unix, this is using the $XDG_DATA_DIRS[0] default. |
582 | + |
583 | + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. |
584 | + """ |
585 | + if sys.platform == "win32": |
586 | + if appauthor is None: |
587 | + appauthor = appname |
588 | + path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) |
589 | + if appname: |
590 | + path = os.path.join(path, appauthor, appname) |
591 | + elif sys.platform == 'darwin': |
592 | + path = os.path.expanduser('/Library/Application Support') |
593 | + if appname: |
594 | + path = os.path.join(path, appname) |
595 | + else: |
596 | + # XDG default for $XDG_DATA_DIRS |
597 | + # only first, if multipath is False |
598 | + path = os.getenv('XDG_DATA_DIRS', |
599 | + os.pathsep.join(['/usr/local/share', '/usr/share'])) |
600 | + pathlist = [ os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) ] |
601 | + if appname: |
602 | + if version: |
603 | + appname = os.path.join(appname, version) |
604 | + pathlist = [ os.sep.join([x, appname]) for x in pathlist ] |
605 | + |
606 | + if multipath: |
607 | + path = os.pathsep.join(pathlist) |
608 | + else: |
609 | + path = pathlist[0] |
610 | + return path |
611 | + |
612 | + if appname and version: |
613 | + path = os.path.join(path, version) |
614 | + return path |
615 | + |
616 | + |
617 | +def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): |
618 | + r"""Return full path to the user-specific config dir for this application. |
619 | + |
620 | + "appname" is the name of application. |
621 | + If None, just the system directory is returned. |
622 | + "appauthor" (only required and used on Windows) is the name of the |
623 | + appauthor or distributing body for this application. Typically |
624 | + it is the owning company name. This falls back to appname. |
625 | + "version" is an optional version path element to append to the |
626 | + path. You might want to use this if you want multiple versions |
627 | + of your app to be able to run independently. If used, this |
628 | + would typically be "<major>.<minor>". |
629 | + Only applied when appname is present. |
630 | + "roaming" (boolean, default False) can be set True to use the Windows |
631 | + roaming appdata directory. That means that for users on a Windows |
632 | + network setup for roaming profiles, this user data will be |
633 | + sync'd on login. See |
634 | + <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> |
635 | + for a discussion of issues. |
636 | + |
637 | + Typical user data directories are: |
638 | + Mac OS X: same as user_data_dir |
639 | + Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined |
640 | + Win *: same as user_data_dir |
641 | + |
642 | + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. |
643 | + That means, by deafult "~/.local/share/<AppName>". |
644 | + """ |
645 | + if sys.platform in [ "win32", "darwin" ]: |
646 | + path = user_data_dir(appname, appauthor, None, roaming) |
647 | + else: |
648 | + path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) |
649 | + if appname: |
650 | + path = os.path.join(path, appname) |
651 | + if appname and version: |
652 | + path = os.path.join(path, version) |
653 | + return path |
654 | + |
655 | + |
656 | +def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): |
657 | + """Return full path to the user-shared data dir for this application. |
658 | + |
659 | + "appname" is the name of application. |
660 | + If None, just the system directory is returned. |
661 | + "appauthor" (only required and used on Windows) is the name of the |
662 | + appauthor or distributing body for this application. Typically |
663 | + it is the owning company name. This falls back to appname. |
664 | + "version" is an optional version path element to append to the |
665 | + path. You might want to use this if you want multiple versions |
666 | + of your app to be able to run independently. If used, this |
667 | + would typically be "<major>.<minor>". |
668 | + Only applied when appname is present. |
669 | + "multipath" is an optional parameter only applicable to *nix |
670 | + which indicates that the entire list of config dirs should be |
671 | + returned. By default, the first item from XDG_CONFIG_DIRS is |
672 | + returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set |
673 | + |
674 | + Typical user data directories are: |
675 | + Mac OS X: same as site_data_dir |
676 | + Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in |
677 | + $XDG_CONFIG_DIRS |
678 | + Win *: same as site_data_dir |
679 | + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) |
680 | + |
681 | + For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False |
682 | + |
683 | + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. |
684 | + """ |
685 | + if sys.platform in [ "win32", "darwin" ]: |
686 | + path = site_data_dir(appname, appauthor) |
687 | + if appname and version: |
688 | + path = os.path.join(path, version) |
689 | + else: |
690 | + # XDG default for $XDG_CONFIG_DIRS |
691 | + # only first, if multipath is False |
692 | + path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') |
693 | + pathlist = [ os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep) ] |
694 | + if appname: |
695 | + if version: |
696 | + appname = os.path.join(appname, version) |
697 | + pathlist = [ os.sep.join([x, appname]) for x in pathlist ] |
698 | + |
699 | + if multipath: |
700 | + path = os.pathsep.join(pathlist) |
701 | + else: |
702 | + path = pathlist[0] |
703 | + return path |
704 | + |
705 | +def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): |
706 | + r"""Return full path to the user-specific cache dir for this application. |
707 | + |
708 | + "appname" is the name of application. |
709 | + If None, just the system directory is returned. |
710 | + "appauthor" (only required and used on Windows) is the name of the |
711 | + appauthor or distributing body for this application. Typically |
712 | + it is the owning company name. This falls back to appname. |
713 | + "version" is an optional version path element to append to the |
714 | + path. You might want to use this if you want multiple versions |
715 | + of your app to be able to run independently. If used, this |
716 | + would typically be "<major>.<minor>". |
717 | + Only applied when appname is present. |
718 | + "opinion" (boolean) can be False to disable the appending of |
719 | + "Cache" to the base app data dir for Windows. See |
720 | + discussion below. |
721 | + |
722 | + Typical user cache directories are: |
723 | + Mac OS X: ~/Library/Caches/<AppName> |
724 | + Unix: ~/.cache/<AppName> (XDG default) |
725 | + Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache |
726 | + Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache |
727 | + |
728 | + On Windows the only suggestion in the MSDN docs is that local settings go in |
729 | + the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming |
730 | + app data dir (the default returned by `user_data_dir` above). Apps typically |
731 | + put cache data somewhere *under* the given dir here. Some examples: |
732 | + ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache |
733 | + ...\Acme\SuperApp\Cache\1.0 |
734 | + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. |
735 | + This can be disabled with the `opinion=False` option. |
736 | + """ |
737 | + if sys.platform == "win32": |
738 | + if appauthor is None: |
739 | + appauthor = appname |
740 | + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) |
741 | + if appname: |
742 | + path = os.path.join(path, appauthor, appname) |
743 | + if opinion: |
744 | + path = os.path.join(path, "Cache") |
745 | + elif sys.platform == 'darwin': |
746 | + path = os.path.expanduser('~/Library/Caches') |
747 | + if appname: |
748 | + path = os.path.join(path, appname) |
749 | + else: |
750 | + path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) |
751 | + if appname: |
752 | + path = os.path.join(path, appname) |
753 | + if appname and version: |
754 | + path = os.path.join(path, version) |
755 | + return path |
756 | + |
757 | +def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): |
758 | + r"""Return full path to the user-specific log dir for this application. |
759 | + |
760 | + "appname" is the name of application. |
761 | + If None, just the system directory is returned. |
762 | + "appauthor" (only required and used on Windows) is the name of the |
763 | + appauthor or distributing body for this application. Typically |
764 | + it is the owning company name. This falls back to appname. |
765 | + "version" is an optional version path element to append to the |
766 | + path. You might want to use this if you want multiple versions |
767 | + of your app to be able to run independently. If used, this |
768 | + would typically be "<major>.<minor>". |
769 | + Only applied when appname is present. |
770 | + "opinion" (boolean) can be False to disable the appending of |
771 | + "Logs" to the base app data dir for Windows, and "log" to the |
772 | + base cache dir for Unix. See discussion below. |
773 | + |
774 | + Typical user cache directories are: |
775 | + Mac OS X: ~/Library/Logs/<AppName> |
776 | + Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined |
777 | + Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs |
778 | + Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs |
779 | + |
780 | + On Windows the only suggestion in the MSDN docs is that local settings |
781 | + go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in |
782 | + examples of what some windows apps use for a logs dir.) |
783 | + |
784 | + OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` |
785 | + value for Windows and appends "log" to the user cache dir for Unix. |
786 | + This can be disabled with the `opinion=False` option. |
787 | + """ |
788 | + if sys.platform == "darwin": |
789 | + path = os.path.join( |
790 | + os.path.expanduser('~/Library/Logs'), |
791 | + appname) |
792 | + elif sys.platform == "win32": |
793 | + path = user_data_dir(appname, appauthor, version); version=False |
794 | + if opinion: |
795 | + path = os.path.join(path, "Logs") |
796 | + else: |
797 | + path = user_cache_dir(appname, appauthor, version); version=False |
798 | + if opinion: |
799 | + path = os.path.join(path, "log") |
800 | + if appname and version: |
801 | + path = os.path.join(path, version) |
802 | + return path |
803 | + |
804 | + |
805 | +class AppDirs(object): |
806 | + """Convenience wrapper for getting application dirs.""" |
807 | + def __init__(self, appname, appauthor=None, version=None, |
808 | + roaming=False, multipath=False): |
809 | + self.appname = appname |
810 | + self.appauthor = appauthor |
811 | + self.version = version |
812 | + self.roaming = roaming |
813 | + self.multipath = multipath |
814 | + @property |
815 | + def user_data_dir(self): |
816 | + return user_data_dir(self.appname, self.appauthor, |
817 | + version=self.version, roaming=self.roaming) |
818 | + @property |
819 | + def site_data_dir(self): |
820 | + return site_data_dir(self.appname, self.appauthor, |
821 | + version=self.version, multipath=self.multipath) |
822 | + @property |
823 | + def user_config_dir(self): |
824 | + return user_config_dir(self.appname, self.appauthor, |
825 | + version=self.version, roaming=self.roaming) |
826 | + @property |
827 | + def site_config_dir(self): |
828 | + return site_data_dir(self.appname, self.appauthor, |
829 | + version=self.version, multipath=self.multipath) |
830 | + @property |
831 | + def user_cache_dir(self): |
832 | + return user_cache_dir(self.appname, self.appauthor, |
833 | + version=self.version) |
834 | + @property |
835 | + def user_log_dir(self): |
836 | + return user_log_dir(self.appname, self.appauthor, |
837 | + version=self.version) |
838 | + |
839 | + |
840 | + |
841 | + |
842 | +#---- internal support stuff |
843 | + |
844 | +def _get_win_folder_from_registry(csidl_name): |
845 | + """This is a fallback technique at best. I'm not sure if using the |
846 | + registry for this guarantees us the correct answer for all CSIDL_* |
847 | + names. |
848 | + """ |
849 | + import _winreg |
850 | + |
851 | + shell_folder_name = { |
852 | + "CSIDL_APPDATA": "AppData", |
853 | + "CSIDL_COMMON_APPDATA": "Common AppData", |
854 | + "CSIDL_LOCAL_APPDATA": "Local AppData", |
855 | + }[csidl_name] |
856 | + |
857 | + key = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, |
858 | + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") |
859 | + dir, type = _winreg.QueryValueEx(key, shell_folder_name) |
860 | + return dir |
861 | + |
862 | +def _get_win_folder_with_pywin32(csidl_name): |
863 | + from win32com.shell import shellcon, shell |
864 | + dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) |
865 | + # Try to make this a unicode path because SHGetFolderPath does |
866 | + # not return unicode strings when there is unicode data in the |
867 | + # path. |
868 | + try: |
869 | + dir = unicode(dir) |
870 | + |
871 | + # Downgrade to short path name if have highbit chars. See |
872 | + # <http://bugs.activestate.com/show_bug.cgi?id=85099>. |
873 | + has_high_char = False |
874 | + for c in dir: |
875 | + if ord(c) > 255: |
876 | + has_high_char = True |
877 | + break |
878 | + if has_high_char: |
879 | + try: |
880 | + import win32api |
881 | + dir = win32api.GetShortPathName(dir) |
882 | + except ImportError: |
883 | + pass |
884 | + except UnicodeError: |
885 | + pass |
886 | + return dir |
887 | + |
888 | +def _get_win_folder_with_ctypes(csidl_name): |
889 | + import ctypes |
890 | + |
891 | + csidl_const = { |
892 | + "CSIDL_APPDATA": 26, |
893 | + "CSIDL_COMMON_APPDATA": 35, |
894 | + "CSIDL_LOCAL_APPDATA": 28, |
895 | + }[csidl_name] |
896 | + |
897 | + buf = ctypes.create_unicode_buffer(1024) |
898 | + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) |
899 | + |
900 | + # Downgrade to short path name if have highbit chars. See |
901 | + # <http://bugs.activestate.com/show_bug.cgi?id=85099>. |
902 | + has_high_char = False |
903 | + for c in buf: |
904 | + if ord(c) > 255: |
905 | + has_high_char = True |
906 | + break |
907 | + if has_high_char: |
908 | + buf2 = ctypes.create_unicode_buffer(1024) |
909 | + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): |
910 | + buf = buf2 |
911 | + |
912 | + return buf.value |
913 | + |
914 | +if sys.platform == "win32": |
915 | + try: |
916 | + import win32com.shell |
917 | + _get_win_folder = _get_win_folder_with_pywin32 |
918 | + except ImportError: |
919 | + try: |
920 | + import ctypes |
921 | + _get_win_folder = _get_win_folder_with_ctypes |
922 | + except ImportError: |
923 | + _get_win_folder = _get_win_folder_from_registry |
924 | + |
925 | + |
926 | + |
927 | +#---- self test code |
928 | + |
929 | +if __name__ == "__main__": |
930 | + appname = "MyApp" |
931 | + appauthor = "MyCompany" |
932 | + |
933 | + props = ("user_data_dir", "site_data_dir", |
934 | + "user_config_dir", "site_config_dir", |
935 | + "user_cache_dir", "user_log_dir") |
936 | + |
937 | + print("-- app dirs (with optional 'version')") |
938 | + dirs = AppDirs(appname, appauthor, version="1.0") |
939 | + for prop in props: |
940 | + print("%s: %s" % (prop, getattr(dirs, prop))) |
941 | + |
942 | + print("\n-- app dirs (without optional 'version')") |
943 | + dirs = AppDirs(appname, appauthor) |
944 | + for prop in props: |
945 | + print("%s: %s" % (prop, getattr(dirs, prop))) |
946 | + |
947 | + print("\n-- app dirs (without optional 'appauthor')") |
948 | + dirs = AppDirs(appname) |
949 | + for prop in props: |
950 | + print("%s: %s" % (prop, getattr(dirs, prop))) |
951 | + |
952 | |
953 | === modified file 'openerp/tools/config.py' |
954 | --- openerp/tools/config.py 2014-02-12 22:52:40 +0000 |
955 | +++ openerp/tools/config.py 2014-02-27 16:58:29 +0000 |
956 | @@ -3,7 +3,7 @@ |
957 | # |
958 | # OpenERP, Open Source Management Solution |
959 | # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). |
960 | -# Copyright (C) 2010-2012 OpenERP s.a. (<http://openerp.com>). |
961 | +# Copyright (C) 2010-2014 OpenERP s.a. (<http://openerp.com>). |
962 | # |
963 | # This program is free software: you can redistribute it and/or modify |
964 | # it under the terms of the GNU Affero General Public License as |
965 | @@ -29,6 +29,7 @@ |
966 | import openerp.loglevels as loglevels |
967 | import logging |
968 | import openerp.release as release |
969 | +import appdirs |
970 | |
971 | class MyOption (optparse.Option, object): |
972 | """ optparse Option with two additional attributes. |
973 | @@ -59,6 +60,9 @@ |
974 | |
975 | DEFAULT_LOG_HANDLER = [':INFO'] |
976 | |
977 | +def _get_default_datadir(): |
978 | + return appdirs.user_data_dir(appname='OpenERP', appauthor=release.author) |
979 | + |
980 | class configmanager(object): |
981 | def __init__(self, fname=None): |
982 | # Options not exposed on the command line. Command line options will be added |
983 | @@ -106,6 +110,9 @@ |
984 | help="specify additional addons paths (separated by commas).", |
985 | action="callback", callback=self._check_addons_path, nargs=1, type="string") |
986 | group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules default=web") |
987 | + |
988 | + group.add_option("-D", "--data-dir", dest="data_dir", my_default=_get_default_datadir(), |
989 | + help="Directory where to store OpenERP data") |
990 | parser.add_option_group(group) |
991 | |
992 | # XML-RPC / HTTP |
993 | @@ -348,6 +355,7 @@ |
994 | # (../etc from the server) |
995 | # if the server is run by an unprivileged user, he has to specify location of a config file where he has the rights to write, |
996 | # else he won't be able to save the configurations, or even to start the server... |
997 | + # TODO use appdirs |
998 | if os.name == 'nt': |
999 | rcfilepath = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'openerp-server.conf') |
1000 | else: |
1001 | @@ -358,7 +366,6 @@ |
1002 | or os.environ.get('OPENERP_SERVER') or rcfilepath) |
1003 | self.load() |
1004 | |
1005 | - |
1006 | # Verify that we want to log or not, if not the output will go to stdout |
1007 | if self.options['logfile'] in ('None', 'False'): |
1008 | self.options['logfile'] = False |
1009 | @@ -387,7 +394,6 @@ |
1010 | elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER: |
1011 | self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg]) |
1012 | |
1013 | - |
1014 | if isinstance(self.options['log_handler'], basestring): |
1015 | self.options['log_handler'] = self.options['log_handler'].split(',') |
1016 | |
1017 | @@ -399,7 +405,8 @@ |
1018 | 'list_db', 'xmlrpcs', 'proxy_mode', |
1019 | 'test_file', 'test_enable', 'test_commit', 'test_report_directory', |
1020 | 'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent', |
1021 | - 'workers', 'limit_memory_hard', 'limit_memory_soft', 'limit_time_cpu', 'limit_time_real', 'limit_request', 'auto_reload' |
1022 | + 'workers', 'limit_memory_hard', 'limit_memory_soft', 'limit_time_cpu', 'limit_time_real', 'limit_request', |
1023 | + 'auto_reload', 'data_dir', |
1024 | ] |
1025 | |
1026 | for arg in keys: |
1027 | @@ -617,6 +624,24 @@ |
1028 | def __getitem__(self, key): |
1029 | return self.options[key] |
1030 | |
1031 | + @property |
1032 | + def addons_data_dir(self): |
1033 | + d = os.path.join(self['data_dir'], 'addons', release.series) |
1034 | + if not os.path.exists(d): |
1035 | + os.makedirs(d, 0700) |
1036 | + else: |
1037 | + os.chmod(d, 0700) |
1038 | + return d |
1039 | + |
1040 | + @property |
1041 | + def session_dir(self): |
1042 | + d = os.path.join(self['data_dir'], 'sessions', release.series) |
1043 | + if not os.path.exists(d): |
1044 | + os.makedirs(d, 0700) |
1045 | + else: |
1046 | + os.chmod(d, 0700) |
1047 | + return d |
1048 | + |
1049 | config = configmanager() |
1050 | |
1051 | |
1052 | |
1053 | === modified file 'openerp/tools/translate.py' |
1054 | --- openerp/tools/translate.py 2014-02-06 11:02:20 +0000 |
1055 | +++ openerp/tools/translate.py 2014-02-27 16:58:29 +0000 |
1056 | @@ -778,49 +778,32 @@ |
1057 | if model_obj._sql_constraints: |
1058 | push_local_constraints(module, model_obj, 'sql_constraints') |
1059 | |
1060 | - def get_module_from_path(path, mod_paths=None): |
1061 | - if not mod_paths: |
1062 | - # First, construct a list of possible paths |
1063 | - def_path = os.path.abspath(os.path.join(config.config['root_path'], 'addons')) # default addons path (base) |
1064 | - ad_paths= map(lambda m: os.path.abspath(m.strip()),config.config['addons_path'].split(',')) |
1065 | - mod_paths=[def_path] |
1066 | - for adp in ad_paths: |
1067 | - mod_paths.append(adp) |
1068 | - if not os.path.isabs(adp): |
1069 | - mod_paths.append(adp) |
1070 | - elif adp.startswith(def_path): |
1071 | - mod_paths.append(adp[len(def_path)+1:]) |
1072 | - for mp in mod_paths: |
1073 | - if path.startswith(mp) and (os.path.dirname(path) != mp): |
1074 | - path = path[len(mp)+1:] |
1075 | - return path.split(os.path.sep)[0] |
1076 | - return 'base' # files that are not in a module are considered as being in 'base' module |
1077 | |
1078 | modobj = registry['ir.module.module'] |
1079 | installed_modids = modobj.search(cr, uid, [('state', '=', 'installed')]) |
1080 | installed_modules = map(lambda m: m['name'], modobj.read(cr, uid, installed_modids, ['name'])) |
1081 | |
1082 | - root_path = os.path.join(config.config['root_path'], 'addons') |
1083 | - |
1084 | - apaths = map(os.path.abspath, map(str.strip, config.config['addons_path'].split(','))) |
1085 | - if root_path in apaths: |
1086 | - path_list = apaths |
1087 | - else : |
1088 | - path_list = [root_path,] + apaths |
1089 | - |
1090 | + path_list = list(openerp.modules.module.ad_paths) |
1091 | # Also scan these non-addon paths |
1092 | for bin_path in ['osv', 'report' ]: |
1093 | path_list.append(os.path.join(config.config['root_path'], bin_path)) |
1094 | |
1095 | _logger.debug("Scanning modules at paths: ", path_list) |
1096 | |
1097 | - mod_paths = [] |
1098 | + mod_paths = list(path_list) |
1099 | + |
1100 | + def get_module_from_path(path): |
1101 | + for mp in mod_paths: |
1102 | + if path.startswith(mp) and (os.path.dirname(path) != mp): |
1103 | + path = path[len(mp)+1:] |
1104 | + return path.split(os.path.sep)[0] |
1105 | + return 'base' # files that are not in a module are considered as being in 'base' module |
1106 | |
1107 | def verified_module_filepaths(fname, path, root): |
1108 | fabsolutepath = join(root, fname) |
1109 | frelativepath = fabsolutepath[len(path):] |
1110 | display_path = "addons%s" % frelativepath |
1111 | - module = get_module_from_path(fabsolutepath, mod_paths=mod_paths) |
1112 | + module = get_module_from_path(fabsolutepath) |
1113 | if ('all' in modules or module in modules) and module in installed_modules: |
1114 | return module, fabsolutepath, frelativepath, display_path |
1115 | return None, None, None, None |