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