[MERGE] latest trunk
[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', 'stop_after_init'])
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("--addons-path", dest="addons_path",
106                          help="specify additional addons paths (separated by commas).",
107                          action="callback", callback=self._check_addons_path, nargs=1, type="string")
108         group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules default=web")
109         parser.add_option_group(group)
110
111         # XML-RPC / HTTP
112         group = optparse.OptionGroup(parser, "XML-RPC Configuration")
113         group.add_option("--xmlrpc-interface", dest="xmlrpc_interface", my_default='',
114                          help="Specify the TCP IP address for the XML-RPC protocol. The empty string binds to all interfaces.")
115         group.add_option("--xmlrpc-port", dest="xmlrpc_port", my_default=8069,
116                          help="specify the TCP port for the XML-RPC protocol", type="int")
117         group.add_option("--no-xmlrpc", dest="xmlrpc", action="store_false", my_default=True,
118                          help="disable the XML-RPC protocol")
119         parser.add_option_group(group)
120
121         # XML-RPC / HTTPS
122         title = "XML-RPC Secure Configuration"
123         if not self.has_ssl:
124             title += " (disabled as ssl is unavailable)"
125
126         group = optparse.OptionGroup(parser, title)
127         group.add_option("--xmlrpcs-interface", dest="xmlrpcs_interface", my_default='',
128                          help="Specify the TCP IP address for the XML-RPC Secure protocol. The empty string binds to all interfaces.")
129         group.add_option("--xmlrpcs-port", dest="xmlrpcs_port", my_default=8071,
130                          help="specify the TCP port for the XML-RPC Secure protocol", type="int")
131         group.add_option("--no-xmlrpcs", dest="xmlrpcs", action="store_false", my_default=True,
132                          help="disable the XML-RPC Secure protocol")
133         group.add_option("--cert-file", dest="secure_cert_file", my_default='server.cert',
134                          help="specify the certificate file for the SSL connection")
135         group.add_option("--pkey-file", dest="secure_pkey_file", my_default='server.pkey',
136                          help="specify the private key file for the SSL connection")
137         parser.add_option_group(group)
138
139         # NET-RPC
140         group = optparse.OptionGroup(parser, "NET-RPC Configuration")
141         group.add_option("--netrpc-interface", dest="netrpc_interface", my_default='',
142                          help="specify the TCP IP address for the NETRPC protocol")
143         group.add_option("--netrpc-port", dest="netrpc_port", my_default=8070,
144                          help="specify the TCP port for the NETRPC protocol", type="int")
145         group.add_option("--no-netrpc", dest="netrpc", action="store_false", my_default=True,
146                          help="disable the NETRPC protocol")
147         parser.add_option_group(group)
148
149         # WEB
150         # TODO move to web addons after MetaOption merge
151         group = optparse.OptionGroup(parser, "Web interface Configuration")
152         group.add_option("--db-filter", dest="dbfilter", default='.*',
153                          help="Filter listed database", metavar="REGEXP")
154         parser.add_option_group(group)
155
156         # Static HTTP
157         group = optparse.OptionGroup(parser, "Static HTTP service")
158         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")
159         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/')")
160         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 '/')")
161         parser.add_option_group(group)
162
163         # Testing Group
164         group = optparse.OptionGroup(parser, "Testing Configuration")
165         group.add_option("--test-file", dest="test_file", my_default=False,
166                          help="Launch a YML test file.")
167         group.add_option("--test-report-directory", dest="test_report_directory", my_default=False,
168                          help="If set, will save sample of all reports in this directory.")
169         group.add_option("--test-disable", action="store_true", dest="test_disable",
170                          my_default=False, help="Disable loading test files.")
171         group.add_option("--test-commit", action="store_true", dest="test_commit",
172                          my_default=False, help="Commit database changes performed by tests.")
173         group.add_option("--assert-exit-level", dest='assert_exit_level', type="choice", choices=self._LOGLEVELS.keys(),
174                          my_default='error',
175                          help="specify the level at which a failed assertion will stop the server. Accepted values: %s" % (self._LOGLEVELS.keys(),))
176         parser.add_option_group(group)
177
178         # Logging Group
179         group = optparse.OptionGroup(parser, "Logging Configuration")
180         group.add_option("--logfile", dest="logfile", help="file where the server log will be stored")
181         group.add_option("--no-logrotate", dest="logrotate", action="store_false", my_default=True,
182                          help="do not rotate the logfile")
183         group.add_option("--syslog", action="store_true", dest="syslog",
184                          my_default=False, help="Send the log to the syslog server")
185         group.add_option('--log-level', dest='log_level', type='choice', choices=self._LOGLEVELS.keys(),
186                          my_default='info',
187                          help='specify the level of the logging. Accepted values: ' + str(self._LOGLEVELS.keys()))
188         parser.add_option_group(group)
189
190         # SMTP Group
191         group = optparse.OptionGroup(parser, "SMTP Configuration")
192         group.add_option('--email-from', dest='email_from', my_default=False,
193                          help='specify the SMTP email address for sending email')
194         group.add_option('--smtp', dest='smtp_server', my_default='localhost',
195                          help='specify the SMTP server for sending email')
196         group.add_option('--smtp-port', dest='smtp_port', my_default=25,
197                          help='specify the SMTP port', type="int")
198         group.add_option('--smtp-ssl', dest='smtp_ssl', action='store_true', my_default=False,
199                          help='specify the SMTP server support SSL or not')
200         group.add_option('--smtp-user', dest='smtp_user', my_default=False,
201                          help='specify the SMTP username for sending email')
202         group.add_option('--smtp-password', dest='smtp_password', my_default=False,
203                          help='specify the SMTP password for sending email')
204         parser.add_option_group(group)
205
206         group = optparse.OptionGroup(parser, "Database related options")
207         group.add_option("-d", "--database", dest="db_name", my_default=False,
208                          help="specify the database name")
209         group.add_option("-r", "--db_user", dest="db_user", my_default=False,
210                          help="specify the database user name")
211         group.add_option("-w", "--db_password", dest="db_password", my_default=False,
212                          help="specify the database password")
213         group.add_option("--pg_path", dest="pg_path", help="specify the pg executable path")
214         group.add_option("--db_host", dest="db_host", my_default=False,
215                          help="specify the database host")
216         group.add_option("--db_port", dest="db_port", my_default=False,
217                          help="specify the database port", type="int")
218         group.add_option("--db_maxconn", dest="db_maxconn", type='int', my_default=64,
219                          help="specify the the maximum number of physical connections to posgresql")
220         parser.add_option_group(group)
221
222         group = optparse.OptionGroup(parser, "Internationalisation options",
223             "Use these options to translate OpenERP to another language."
224             "See i18n section of the user manual. Option '-d' is mandatory."
225             "Option '-l' is mandatory in case of importation"
226             )
227         group.add_option('--load-language', dest="load_language",
228                          help="specifies the languages for the translations you want to be loaded")
229         group.add_option('-l', "--language", dest="language",
230                          help="specify the language of the translation file. Use it with --i18n-export or --i18n-import")
231         group.add_option("--i18n-export", dest="translate_out",
232                          help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit")
233         group.add_option("--i18n-import", dest="translate_in",
234                          help="import a CSV or a PO file with translations and exit. The '-l' option is required.")
235         group.add_option("--i18n-overwrite", dest="overwrite_existing_translations", action="store_true", my_default=False,
236                          help="overwrites existing translation terms on updating a module or importing a CSV or a PO file.")
237         group.add_option("--modules", dest="translate_modules",
238                          help="specify modules to export. Use in combination with --i18n-export")
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         if opt.server_wide_modules:
458             openerp.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(','))
459         else:
460             openerp.conf.server_wide_modules = ['web']
461         if complete:
462             openerp.modules.module.initialize_sys_path()
463             openerp.modules.loading.open_openerp_namespace()
464             # openerp.addons.__path__.extend(openerp.conf.addons_paths) # This
465             # is not compatible with initialize_sys_path(): import crm and
466             # import openerp.addons.crm load twice the module.
467
468     def _generate_pgpassfile(self):
469         """
470         Generate the pgpass file with the parameters from the command line (db_host, db_user,
471         db_password)
472
473         Used because pg_dump and pg_restore can not accept the password on the command line.
474         """
475         is_win32 = sys.platform == 'win32'
476         if is_win32:
477             filename = os.path.join(os.environ['APPDATA'], 'pgpass.conf')
478         else:
479             filename = os.path.join(os.environ['HOME'], '.pgpass')
480
481         text_to_add = "%(db_host)s:*:*:%(db_user)s:%(db_password)s" % self.options
482
483         if os.path.exists(filename):
484             content = [x.strip() for x in file(filename, 'r').readlines()]
485             if text_to_add in content:
486                 return
487
488         fp = file(filename, 'a+')
489         fp.write(text_to_add + "\n")
490         fp.close()
491
492         if is_win32:
493             try:
494                 import _winreg
495             except ImportError:
496                 _winreg = None
497             x=_winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
498             y = _winreg.OpenKey(x, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", 0,_winreg.KEY_ALL_ACCESS)
499             _winreg.SetValueEx(y,"PGPASSFILE", 0, _winreg.REG_EXPAND_SZ, filename )
500             _winreg.CloseKey(y)
501             _winreg.CloseKey(x)
502         else:
503             import stat
504             os.chmod(filename, stat.S_IRUSR + stat.S_IWUSR)
505
506     def _is_addons_path(self, path):
507         for f in os.listdir(path):
508             modpath = os.path.join(path, f)
509             if os.path.isdir(modpath):
510                 def hasfile(filename):
511                     return os.path.isfile(os.path.join(modpath, filename))
512                 if hasfile('__init__.py') and (hasfile('__openerp__.py') or hasfile('__terp__.py')):
513                     return True
514         return False
515
516     def _check_addons_path(self, option, opt, value, parser):
517         ad_paths = []
518         for path in value.split(','):
519             path = path.strip()
520             res = os.path.abspath(os.path.expanduser(path))
521             if not os.path.isdir(res):
522                 raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, path))
523             if not self._is_addons_path(res):
524                 raise optparse.OptionValueError("option %s: The addons-path %r does not seem to a be a valid Addons Directory!" % (opt, path))
525             ad_paths.append(res)
526
527         setattr(parser.values, option.dest, ",".join(ad_paths))
528
529     def load(self):
530         p = ConfigParser.ConfigParser()
531         try:
532             p.read([self.rcfile])
533             for (name,value) in p.items('options'):
534                 if value=='True' or value=='true':
535                     value = True
536                 if value=='False' or value=='false':
537                     value = False
538                 self.options[name] = value
539             #parse the other sections, as well
540             for sec in p.sections():
541                 if sec == 'options':
542                     continue
543                 if not self.misc.has_key(sec):
544                     self.misc[sec]= {}
545                 for (name, value) in p.items(sec):
546                     if value=='True' or value=='true':
547                         value = True
548                     if value=='False' or value=='false':
549                         value = False
550                     self.misc[sec][name] = value
551         except IOError:
552             pass
553         except ConfigParser.NoSectionError:
554             pass
555
556     def save(self):
557         p = ConfigParser.ConfigParser()
558         loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS.keys()))
559         p.add_section('options')
560         for opt in sorted(self.options.keys()):
561             if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'):
562                 continue
563             if opt in self.blacklist_for_save:
564                 continue
565             if opt in ('log_level', 'assert_exit_level'):
566                 p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt]))
567             else:
568                 p.set('options', opt, self.options[opt])
569
570         for sec in sorted(self.misc.keys()):
571             for opt in sorted(self.misc[sec].keys()):
572                 p.set(sec,opt,self.misc[sec][opt])
573
574         # try to create the directories and write the file
575         try:
576             rc_exists = os.path.exists(self.rcfile)
577             if not rc_exists and not os.path.exists(os.path.dirname(self.rcfile)):
578                 os.makedirs(os.path.dirname(self.rcfile))
579             try:
580                 p.write(file(self.rcfile, 'w'))
581                 if not rc_exists:
582                     os.chmod(self.rcfile, 0600)
583             except IOError:
584                 sys.stderr.write("ERROR: couldn't write the config file\n")
585
586         except OSError:
587             # what to do if impossible?
588             sys.stderr.write("ERROR: couldn't create the config directory\n")
589
590     def get(self, key, default=None):
591         return self.options.get(key, default)
592
593     def get_misc(self, sect, key, default=None):
594         return self.misc.get(sect,{}).get(key, default)
595
596     def __setitem__(self, key, value):
597         self.options[key] = value
598
599     def __getitem__(self, key):
600         return self.options[key]
601
602 config = configmanager()
603
604
605 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: