[imp] improved logging, now blacklist openerplib
[odoo/odoo.git] / openerp / tools / config.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import ConfigParser
23 import optparse
24 import os
25 import sys
26 import openerp
27 import openerp.conf
28 import openerp.loglevels as loglevels
29 import logging
30 import openerp.release as release
31
32 class MyOption (optparse.Option, object):
33     """ optparse Option with two additional attributes.
34
35     The list of command line options (getopt.Option) is used to create the
36     list of the configuration file options. When reading the file, and then
37     reading the command line arguments, we don't want optparse.parse results
38     to override the configuration file values. But if we provide default
39     values to optparse, optparse will return them and we can't know if they
40     were really provided by the user or not. A solution is to not use
41     optparse's default attribute, but use a custom one (that will be copied
42     to create the default values of the configuration file).
43
44     """
45     def __init__(self, *opts, **attrs):
46         self.my_default = attrs.pop('my_default', None)
47         super(MyOption, self).__init__(*opts, **attrs)
48
49 #.apidoc title: Server Configuration Loader
50
51 def check_ssl():
52     try:
53         from OpenSSL import SSL
54         import socket
55
56         return hasattr(socket, 'ssl') and hasattr(SSL, "Connection")
57     except:
58         return False
59
60 class configmanager(object):
61     def __init__(self, fname=None):
62         # Options not exposed on the command line. Command line options will be added
63         # from optparse's parser.
64         self.options = {
65             'admin_passwd': 'admin',
66             'csv_internal_sep': ',',
67             'login_message': False,
68             'publisher_warranty_url': 'http://services.openerp.com/publisher-warranty/',
69             'reportgz': False,
70             'root_path': None,
71         }
72
73         # Not exposed in the configuration file.
74         self.blacklist_for_save = set(
75             ['publisher_warranty_url', 'load_language', 'root_path',
76             'init', 'save', 'config', 'update'])
77
78         # dictionary mapping option destination (keys in self.options) to MyOptions.
79         self.casts = {}
80
81         self.misc = {}
82         self.config_file = fname
83         self.has_ssl = check_ssl()
84
85         self._LOGLEVELS = dict([(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x))
86                           for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'TEST', 'DEBUG', 'DEBUG_RPC', 'DEBUG_SQL', 'DEBUG_RPC_ANSWER','NOTSET')])
87
88         version = "%s %s" % (release.description, release.version)
89         self.parser = parser = optparse.OptionParser(version=version, option_class=MyOption)
90
91         # Server startup config
92         group = optparse.OptionGroup(parser, "Common options")
93         group.add_option("-c", "--config", dest="config", help="specify alternate config file")
94         group.add_option("-s", "--save", action="store_true", dest="save", default=False,
95                           help="save configuration to ~/.openerp_serverrc")
96         group.add_option("-i", "--init", dest="init", help="install one or more modules (comma-separated list, use \"all\" for all modules), requires -d")
97         group.add_option("-u", "--update", dest="update",
98                           help="update one or more modules (comma-separated list, use \"all\" for all modules). Requires -d.")
99         group.add_option("--without-demo", dest="without_demo",
100                           help="disable loading demo data for modules to be installed (comma-separated, use \"all\" for all modules). Requires -d and -i. Default is %default",
101                           my_default=False)
102         group.add_option("-P", "--import-partial", dest="import_partial", my_default='',
103                         help="Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states.")
104         group.add_option("--pidfile", dest="pidfile", help="file where the server pid will be stored")
105         group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules")
106         parser.add_option_group(group)
107
108         # XML-RPC / HTTP
109         group = optparse.OptionGroup(parser, "XML-RPC Configuration")
110         group.add_option("--xmlrpc-interface", dest="xmlrpc_interface", my_default='',
111                          help="Specify the TCP IP address for the XML-RPC protocol. The empty string binds to all interfaces.")
112         group.add_option("--xmlrpc-port", dest="xmlrpc_port", my_default=8069,
113                          help="specify the TCP port for the XML-RPC protocol", type="int")
114         group.add_option("--no-xmlrpc", dest="xmlrpc", action="store_false", my_default=True,
115                          help="disable the XML-RPC protocol")
116         parser.add_option_group(group)
117
118         # XML-RPC / HTTPS
119         title = "XML-RPC Secure Configuration"
120         if not self.has_ssl:
121             title += " (disabled as ssl is unavailable)"
122
123         group = optparse.OptionGroup(parser, title)
124         group.add_option("--xmlrpcs-interface", dest="xmlrpcs_interface", my_default='',
125                          help="Specify the TCP IP address for the XML-RPC Secure protocol. The empty string binds to all interfaces.")
126         group.add_option("--xmlrpcs-port", dest="xmlrpcs_port", my_default=8071,
127                          help="specify the TCP port for the XML-RPC Secure protocol", type="int")
128         group.add_option("--no-xmlrpcs", dest="xmlrpcs", action="store_false", my_default=True,
129                          help="disable the XML-RPC Secure protocol")
130         group.add_option("--cert-file", dest="secure_cert_file", my_default='server.cert',
131                          help="specify the certificate file for the SSL connection")
132         group.add_option("--pkey-file", dest="secure_pkey_file", my_default='server.pkey',
133                          help="specify the private key file for the SSL connection")
134         parser.add_option_group(group)
135
136         # NET-RPC
137         group = optparse.OptionGroup(parser, "NET-RPC Configuration")
138         group.add_option("--netrpc-interface", dest="netrpc_interface", my_default='',
139                          help="specify the TCP IP address for the NETRPC protocol")
140         group.add_option("--netrpc-port", dest="netrpc_port", my_default=8070,
141                          help="specify the TCP port for the NETRPC protocol", type="int")
142         group.add_option("--no-netrpc", dest="netrpc", action="store_false", my_default=True,
143                          help="disable the NETRPC protocol")
144         parser.add_option_group(group)
145
146         # WEB
147         # TODO move to web addons after MetaOption merge
148         group = optparse.OptionGroup(parser, "Web interface Configuration")
149         group.add_option("--db-filter", dest="dbfilter", default='.*',
150                          help="Filter listed database", metavar="REGEXP")
151         parser.add_option_group(group)
152
153         # Static HTTP
154         group = optparse.OptionGroup(parser, "Static HTTP service")
155         group.add_option("--static-http-enable", dest="static_http_enable", action="store_true", my_default=False, help="enable static HTTP service for serving plain HTML files")
156         group.add_option("--static-http-document-root", dest="static_http_document_root", help="specify the directory containing your static HTML files (e.g '/var/www/')")
157         group.add_option("--static-http-url-prefix", dest="static_http_url_prefix", help="specify the URL root prefix where you want web browsers to access your static HTML files (e.g '/')")
158         parser.add_option_group(group)
159
160         # Testing Group
161         group = optparse.OptionGroup(parser, "Testing Configuration")
162         group.add_option("--test-file", dest="test_file", my_default=False,
163                          help="Launch a YML test file.")
164         group.add_option("--test-report-directory", dest="test_report_directory", my_default=False,
165                          help="If set, will save sample of all reports in this directory.")
166         group.add_option("--test-disable", action="store_true", dest="test_disable",
167                          my_default=False, help="Disable loading test files.")
168         group.add_option("--test-commit", action="store_true", dest="test_commit",
169                          my_default=False, help="Commit database changes performed by tests.")
170         group.add_option("--assert-exit-level", dest='assert_exit_level', type="choice", choices=self._LOGLEVELS.keys(),
171                          my_default='error',
172                          help="specify the level at which a failed assertion will stop the server. Accepted values: %s" % (self._LOGLEVELS.keys(),))
173         parser.add_option_group(group)
174
175         # Logging Group
176         group = optparse.OptionGroup(parser, "Logging Configuration")
177         group.add_option("--logfile", dest="logfile", help="file where the server log will be stored")
178         group.add_option("--no-logrotate", dest="logrotate", action="store_false", my_default=True,
179                          help="do not rotate the logfile")
180         group.add_option("--syslog", action="store_true", dest="syslog",
181                          my_default=False, help="Send the log to the syslog server")
182         group.add_option('--log-level', dest='log_level', type='choice', choices=self._LOGLEVELS.keys(),
183                          my_default='info',
184                          help='specify the level of the logging. Accepted values: ' + str(self._LOGLEVELS.keys()))
185         parser.add_option_group(group)
186
187         # SMTP Group
188         group = optparse.OptionGroup(parser, "SMTP Configuration")
189         group.add_option('--email-from', dest='email_from', my_default=False,
190                          help='specify the SMTP email address for sending email')
191         group.add_option('--smtp', dest='smtp_server', my_default='localhost',
192                          help='specify the SMTP server for sending email')
193         group.add_option('--smtp-port', dest='smtp_port', my_default=25,
194                          help='specify the SMTP port', type="int")
195         group.add_option('--smtp-ssl', dest='smtp_ssl', action='store_true', my_default=False,
196                          help='specify the SMTP server support SSL or not')
197         group.add_option('--smtp-user', dest='smtp_user', my_default=False,
198                          help='specify the SMTP username for sending email')
199         group.add_option('--smtp-password', dest='smtp_password', my_default=False,
200                          help='specify the SMTP password for sending email')
201         parser.add_option_group(group)
202
203         group = optparse.OptionGroup(parser, "Database related options")
204         group.add_option("-d", "--database", dest="db_name", my_default=False,
205                          help="specify the database name")
206         group.add_option("-r", "--db_user", dest="db_user", my_default=False,
207                          help="specify the database user name")
208         group.add_option("-w", "--db_password", dest="db_password", my_default=False,
209                          help="specify the database password")
210         group.add_option("--pg_path", dest="pg_path", help="specify the pg executable path")
211         group.add_option("--db_host", dest="db_host", my_default=False,
212                          help="specify the database host")
213         group.add_option("--db_port", dest="db_port", my_default=False,
214                          help="specify the database port", type="int")
215         group.add_option("--db_maxconn", dest="db_maxconn", type='int', my_default=64,
216                          help="specify the the maximum number of physical connections to posgresql")
217         parser.add_option_group(group)
218
219         group = optparse.OptionGroup(parser, "Internationalisation options",
220             "Use these options to translate OpenERP to another language."
221             "See i18n section of the user manual. Option '-d' is mandatory."
222             "Option '-l' is mandatory in case of importation"
223             )
224         group.add_option('--load-language', dest="load_language",
225                          help="specifies the languages for the translations you want to be loaded")
226         group.add_option('-l', "--language", dest="language",
227                          help="specify the language of the translation file. Use it with --i18n-export or --i18n-import")
228         group.add_option("--i18n-export", dest="translate_out",
229                          help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit")
230         group.add_option("--i18n-import", dest="translate_in",
231                          help="import a CSV or a PO file with translations and exit. The '-l' option is required.")
232         group.add_option("--i18n-overwrite", dest="overwrite_existing_translations", action="store_true", my_default=False,
233                          help="overwrites existing translation terms on updating a module or importing a CSV or a PO file.")
234         group.add_option("--modules", dest="translate_modules",
235                          help="specify modules to export. Use in combination with --i18n-export")
236         group.add_option("--addons-path", dest="addons_path",
237                          help="specify additional addons paths (separated by commas).",
238                          action="callback", callback=self._check_addons_path, nargs=1, type="string")
239         parser.add_option_group(group)
240
241         security = optparse.OptionGroup(parser, 'Security-related options')
242         security.add_option('--no-database-list', action="store_false", dest='list_db', my_default=True,
243                             help="disable the ability to return the list of databases")
244         parser.add_option_group(security)
245
246         # Advanced options
247         group = optparse.OptionGroup(parser, "Advanced options")
248         group.add_option("--cache-timeout", dest="cache_timeout", my_default=100000,
249                           help="set the timeout for the cache system", type="int")
250         group.add_option('--debug', dest='debug_mode', action='store_true', my_default=False, help='enable debug mode')
251         group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False,
252                           help="stop the server after its initialization")
253         group.add_option("-t", "--timezone", dest="timezone", my_default=False,
254                          help="specify reference timezone for the server (e.g. Europe/Brussels")
255         group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=False,
256                          help="Force a limit on the maximum number of records kept in the virtual "
257                               "osv_memory tables. The default is False, which means no count-based limit.",
258                          type="int")
259         group.add_option("--osv-memory-age-limit", dest="osv_memory_age_limit", my_default=1.0,
260                          help="Force a limit on the maximum age of records kept in the virtual "
261                               "osv_memory tables. This is a decimal value expressed in hours, "
262                               "and the default is 1 hour.",
263                          type="float")
264         group.add_option("--max-cron-threads", dest="max_cron_threads", my_default=4,
265                          help="Maximum number of threads processing concurrently cron jobs.",
266                          type="int")
267         group.add_option("--unaccent", dest="unaccent", my_default=False, action="store_true",
268                          help="Use the unaccent function provided by the database when available.")
269
270         parser.add_option_group(group)
271
272         # Copy all optparse options (i.e. MyOption) into self.options.
273         for group in parser.option_groups:
274             for option in group.option_list:
275                 self.options[option.dest] = option.my_default
276                 self.casts[option.dest] = option
277
278         self.parse_config(None, False)
279
280     def parse_config(self, args=None, complete=True):
281         """ Parse the configuration file (if any) and the command-line
282         arguments.
283
284         This method initializes openerp.tools.config and openerp.conf (the
285         former should be removed in the furture) with library-wide
286         configuration values.
287
288         This method must be called before proper usage of this library can be
289         made.
290
291         Typical usage of this method:
292
293             openerp.tools.config.parse_config(sys.argv[1:])
294
295         :param complete: this is a hack used in __init__(), leave it to True.
296
297         """
298         if args is None:
299             args = []
300         opt, args = self.parser.parse_args(args)
301
302         def die(cond, msg):
303             if cond:
304                 self.parser.error(msg)
305
306         # Ensures no illegitimate argument is silently discarded (avoids insidious "hyphen to dash" problem)
307         die(args, "unrecognized parameters: '%s'" % " ".join(args))
308
309         die(bool(opt.syslog) and bool(opt.logfile),
310             "the syslog and logfile options are exclusive")
311
312         die(opt.translate_in and (not opt.language or not opt.db_name),
313             "the i18n-import option cannot be used without the language (-l) and the database (-d) options")
314
315         die(opt.overwrite_existing_translations and not (opt.translate_in or opt.update),
316             "the i18n-overwrite option cannot be used without the i18n-import option or without the update option")
317
318         die(opt.translate_out and (not opt.db_name),
319             "the i18n-export option cannot be used without the database (-d) option")
320
321         # Check if the config file exists (-c used, but not -s)
322         die(not opt.save and opt.config and not os.path.exists(opt.config),
323             "The config file '%s' selected with -c/--config doesn't exist, "\
324             "use -s/--save if you want to generate it"%(opt.config))
325
326         # place/search the config file on Win32 near the server installation
327         # (../etc from the server)
328         # 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,
329         # else he won't be able to save the configurations, or even to start the server...
330         if os.name == 'nt':
331             rcfilepath = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'openerp-server.conf')
332         else:
333             rcfilepath = os.path.expanduser('~/.openerp_serverrc')
334
335         self.rcfile = os.path.abspath(
336             self.config_file or opt.config \
337                 or os.environ.get('OPENERP_SERVER') or rcfilepath)
338         self.load()
339
340
341         # Verify that we want to log or not, if not the output will go to stdout
342         if self.options['logfile'] in ('None', 'False'):
343             self.options['logfile'] = False
344         # the same for the pidfile
345         if self.options['pidfile'] in ('None', 'False'):
346             self.options['pidfile'] = False
347
348         keys = ['xmlrpc_interface', 'xmlrpc_port', 'db_name', 'db_user', 'db_password', 'db_host',
349                 'db_port', 'logfile', 'pidfile', 'smtp_port', 'cache_timeout',
350                 'email_from', 'smtp_server', 'smtp_user', 'smtp_password',
351                 'netrpc_interface', 'netrpc_port', 'db_maxconn', 'import_partial', 'addons_path',
352                 'netrpc', 'xmlrpc', 'syslog', 'without_demo', 'timezone',
353                 'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs',
354                 'static_http_enable', 'static_http_document_root', 'static_http_url_prefix',
355                 'secure_cert_file', 'secure_pkey_file', 'dbfilter'
356                 ]
357
358         for arg in keys:
359             # Copy the command-line argument...
360             if getattr(opt, arg):
361                 self.options[arg] = getattr(opt, arg)
362             # ... or keep, but cast, the config file value.
363             elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
364                 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
365
366         keys = [
367             'language', 'translate_out', 'translate_in', 'overwrite_existing_translations',
368             'debug_mode', 'smtp_ssl', 'load_language',
369             'stop_after_init', 'logrotate', 'without_demo', 'netrpc', 'xmlrpc', 'syslog',
370             'list_db', 'xmlrpcs',
371             'test_file', 'test_disable', 'test_commit', 'test_report_directory',
372             'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
373         ]
374
375         for arg in keys:
376             # Copy the command-line argument...
377             if getattr(opt, arg) is not None:
378                 self.options[arg] = getattr(opt, arg)
379             # ... or keep, but cast, the config file value.
380             elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
381                 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
382
383         if opt.assert_exit_level:
384             self.options['assert_exit_level'] = self._LOGLEVELS[opt.assert_exit_level]
385         else:
386             self.options['assert_exit_level'] = self._LOGLEVELS.get(self.options['assert_exit_level']) or int(self.options['assert_exit_level'])
387
388         if opt.log_level:
389             self.options['log_level'] = self._LOGLEVELS[opt.log_level]
390         else:
391             self.options['log_level'] = self._LOGLEVELS.get(self.options['log_level']) or int(self.options['log_level'])
392
393         self.options['root_path'] = os.path.abspath(os.path.expanduser(os.path.expandvars(os.path.dirname(openerp.__file__))))
394         if not self.options['addons_path'] or self.options['addons_path']=='None':
395             self.options['addons_path'] = os.path.join(self.options['root_path'], 'addons')
396         else:
397             self.options['addons_path'] = ",".join(
398                     os.path.abspath(os.path.expanduser(os.path.expandvars(x)))
399                       for x in self.options['addons_path'].split(','))
400
401         self.options['init'] = opt.init and dict.fromkeys(opt.init.split(','), 1) or {}
402         self.options["demo"] = not opt.without_demo and self.options['init'] or {}
403         self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {}
404         self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all']
405         self.options['translate_modules'].sort()
406
407         # TODO checking the type of the parameters should be done for every
408         # parameters, not just the timezone.
409         # The call to get_server_timezone() sets the timezone; this should
410         # probably done here.
411         if self.options['timezone']:
412             # Prevent the timezone to be True. (The config file parsing changes
413             # the string 'True' to the boolean value True. It would be probably
414             # be better to remove that conversion.)
415             die(not isinstance(self.options['timezone'], basestring),
416                 "Invalid timezone value in configuration or environment: %r.\n"
417                 "Please fix this in your configuration." %(self.options['timezone']))
418
419             # If an explicit TZ was provided in the config, make sure it is known
420             try:
421                 import pytz
422                 pytz.timezone(self.options['timezone'])
423             except pytz.UnknownTimeZoneError:
424                 die(True, "The specified timezone (%s) is invalid" % self.options['timezone'])
425             except:
426                 # If pytz is missing, don't check the provided TZ, it will be ignored anyway.
427                 pass
428
429         if opt.pg_path:
430             self.options['pg_path'] = opt.pg_path
431
432         if self.options.get('language', False):
433             if len(self.options['language']) > 5:
434                 raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE')
435
436         if not self.options['db_user']:
437             try:
438                 import getpass
439                 self.options['db_user'] = getpass.getuser()
440             except:
441                 self.options['db_user'] = None
442
443         die(not self.options['db_user'], 'ERROR: No user specified for the connection to the database')
444
445         if self.options['db_password']:
446             if sys.platform == 'win32' and not self.options['db_host']:
447                 self.options['db_host'] = 'localhost'
448             #if self.options['db_host']:
449             #    self._generate_pgpassfile()
450
451         if opt.save:
452             self.save()
453
454         openerp.conf.max_cron_threads = self.options['max_cron_threads']
455
456         openerp.conf.addons_paths = self.options['addons_path'].split(',')
457         openerp.conf.server_wide_modules = \
458             map(lambda m: m.strip(), opt.server_wide_modules.split(',')) if \
459             opt.server_wide_modules else []
460         if complete:
461             openerp.modules.module.initialize_sys_path()
462             openerp.modules.loading.open_openerp_namespace()
463             # openerp.addons.__path__.extend(openerp.conf.addons_paths) # This
464             # is not compatible with initialize_sys_path(): import crm and
465             # import openerp.addons.crm load twice the module.
466
467     def _generate_pgpassfile(self):
468         """
469         Generate the pgpass file with the parameters from the command line (db_host, db_user,
470         db_password)
471
472         Used because pg_dump and pg_restore can not accept the password on the command line.
473         """
474         is_win32 = sys.platform == 'win32'
475         if is_win32:
476             filename = os.path.join(os.environ['APPDATA'], 'pgpass.conf')
477         else:
478             filename = os.path.join(os.environ['HOME'], '.pgpass')
479
480         text_to_add = "%(db_host)s:*:*:%(db_user)s:%(db_password)s" % self.options
481
482         if os.path.exists(filename):
483             content = [x.strip() for x in file(filename, 'r').readlines()]
484             if text_to_add in content:
485                 return
486
487         fp = file(filename, 'a+')
488         fp.write(text_to_add + "\n")
489         fp.close()
490
491         if is_win32:
492             try:
493                 import _winreg
494             except ImportError:
495                 _winreg = None
496             x=_winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
497             y = _winreg.OpenKey(x, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", 0,_winreg.KEY_ALL_ACCESS)
498             _winreg.SetValueEx(y,"PGPASSFILE", 0, _winreg.REG_EXPAND_SZ, filename )
499             _winreg.CloseKey(y)
500             _winreg.CloseKey(x)
501         else:
502             import stat
503             os.chmod(filename, stat.S_IRUSR + stat.S_IWUSR)
504
505     def _is_addons_path(self, path):
506         for f in os.listdir(path):
507             modpath = os.path.join(path, f)
508             if os.path.isdir(modpath):
509                 def hasfile(filename):
510                     return os.path.isfile(os.path.join(modpath, filename))
511                 if hasfile('__init__.py') and (hasfile('__openerp__.py') or hasfile('__terp__.py')):
512                     return True
513         return False
514
515     def _check_addons_path(self, option, opt, value, parser):
516         ad_paths = []
517         for path in value.split(','):
518             path = path.strip()
519             res = os.path.abspath(os.path.expanduser(path))
520             if not os.path.isdir(res):
521                 raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, path))
522             if not self._is_addons_path(res):
523                 raise optparse.OptionValueError("option %s: The addons-path %r does not seem to a be a valid Addons Directory!" % (opt, path))
524             ad_paths.append(res)
525
526         setattr(parser.values, option.dest, ",".join(ad_paths))
527
528     def load(self):
529         p = ConfigParser.ConfigParser()
530         try:
531             p.read([self.rcfile])
532             for (name,value) in p.items('options'):
533                 if value=='True' or value=='true':
534                     value = True
535                 if value=='False' or value=='false':
536                     value = False
537                 self.options[name] = value
538             #parse the other sections, as well
539             for sec in p.sections():
540                 if sec == 'options':
541                     continue
542                 if not self.misc.has_key(sec):
543                     self.misc[sec]= {}
544                 for (name, value) in p.items(sec):
545                     if value=='True' or value=='true':
546                         value = True
547                     if value=='False' or value=='false':
548                         value = False
549                     self.misc[sec][name] = value
550         except IOError:
551             pass
552         except ConfigParser.NoSectionError:
553             pass
554
555     def save(self):
556         p = ConfigParser.ConfigParser()
557         loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS.keys()))
558         p.add_section('options')
559         for opt in sorted(self.options.keys()):
560             if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'):
561                 continue
562             if opt in self.blacklist_for_save:
563                 continue
564             if opt in ('log_level', 'assert_exit_level'):
565                 p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt]))
566             else:
567                 p.set('options', opt, self.options[opt])
568
569         for sec in sorted(self.misc.keys()):
570             for opt in sorted(self.misc[sec].keys()):
571                 p.set(sec,opt,self.misc[sec][opt])
572
573         # try to create the directories and write the file
574         try:
575             rc_exists = os.path.exists(self.rcfile)
576             if not rc_exists and not os.path.exists(os.path.dirname(self.rcfile)):
577                 os.makedirs(os.path.dirname(self.rcfile))
578             try:
579                 p.write(file(self.rcfile, 'w'))
580                 if not rc_exists:
581                     os.chmod(self.rcfile, 0600)
582             except IOError:
583                 sys.stderr.write("ERROR: couldn't write the config file\n")
584
585         except OSError:
586             # what to do if impossible?
587             sys.stderr.write("ERROR: couldn't create the config directory\n")
588
589     def get(self, key, default=None):
590         return self.options.get(key, default)
591
592     def get_misc(self, sect, key, default=None):
593         return self.misc.get(sect,{}).get(key, default)
594
595     def __setitem__(self, key, value):
596         self.options[key] = value
597
598     def __getitem__(self, key):
599         return self.options[key]
600
601 config = configmanager()
602