[MERGE] forward port of branch 8.0 up to ed1c173
[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         parser.add_option_group(group)
285
286         if os.name == 'posix':
287             group = optparse.OptionGroup(parser, "Multiprocessing options")
288             # TODO sensible default for the three following limits.
289             group.add_option("--workers", dest="workers", my_default=0,
290                              help="Specify the number of workers, 0 disable prefork mode.",
291                              type="int")
292             group.add_option("--limit-memory-soft", dest="limit_memory_soft", my_default=2048 * 1024 * 1024,
293                              help="Maximum allowed virtual memory per worker, when reached the worker be reset after the current request (default 671088640 aka 640MB).",
294                              type="int")
295             group.add_option("--limit-memory-hard", dest="limit_memory_hard", my_default=2560 * 1024 * 1024,
296                              help="Maximum allowed virtual memory per worker, when reached, any memory allocation will fail (default 805306368 aka 768MB).",
297                              type="int")
298             group.add_option("--limit-time-cpu", dest="limit_time_cpu", my_default=60,
299                              help="Maximum allowed CPU time per request (default 60).",
300                              type="int")
301             group.add_option("--limit-time-real", dest="limit_time_real", my_default=120,
302                              help="Maximum allowed Real time per request (default 120).",
303                              type="int")
304             group.add_option("--limit-request", dest="limit_request", my_default=8192,
305                              help="Maximum number of request to be processed per worker (default 8192).",
306                              type="int")
307             parser.add_option_group(group)
308
309         # Copy all optparse options (i.e. MyOption) into self.options.
310         for group in parser.option_groups:
311             for option in group.option_list:
312                 if option.dest not in self.options:
313                     self.options[option.dest] = option.my_default
314                     self.casts[option.dest] = option
315
316         # generate default config
317         self._parse_config()
318
319     def parse_config(self, args=None):
320         """ Parse the configuration file (if any) and the command-line
321         arguments.
322
323         This method initializes openerp.tools.config and openerp.conf (the
324         former should be removed in the furture) with library-wide
325         configuration values.
326
327         This method must be called before proper usage of this library can be
328         made.
329
330         Typical usage of this method:
331
332             openerp.tools.config.parse_config(sys.argv[1:])
333         """
334         self._parse_config(args)
335         openerp.netsvc.init_logger()
336         openerp.modules.module.initialize_sys_path()
337
338     def _parse_config(self, args=None):
339         if args is None:
340             args = []
341         opt, args = self.parser.parse_args(args)
342
343         def die(cond, msg):
344             if cond:
345                 self.parser.error(msg)
346
347         # Ensures no illegitimate argument is silently discarded (avoids insidious "hyphen to dash" problem)
348         die(args, "unrecognized parameters: '%s'" % " ".join(args))
349
350         die(bool(opt.syslog) and bool(opt.logfile),
351             "the syslog and logfile options are exclusive")
352
353         die(opt.translate_in and (not opt.language or not opt.db_name),
354             "the i18n-import option cannot be used without the language (-l) and the database (-d) options")
355
356         die(opt.overwrite_existing_translations and not (opt.translate_in or opt.update),
357             "the i18n-overwrite option cannot be used without the i18n-import option or without the update option")
358
359         die(opt.translate_out and (not opt.db_name),
360             "the i18n-export option cannot be used without the database (-d) option")
361
362         # Check if the config file exists (-c used, but not -s)
363         die(not opt.save and opt.config and not os.path.exists(opt.config),
364             "The config file '%s' selected with -c/--config doesn't exist, "\
365             "use -s/--save if you want to generate it"% opt.config)
366
367         # place/search the config file on Win32 near the server installation
368         # (../etc from the server)
369         # 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,
370         # else he won't be able to save the configurations, or even to start the server...
371         # TODO use appdirs
372         if os.name == 'nt':
373             rcfilepath = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'openerp-server.conf')
374         else:
375             rcfilepath = os.path.expanduser('~/.openerp_serverrc')
376
377         self.rcfile = os.path.abspath(
378             self.config_file or opt.config \
379                 or os.environ.get('OPENERP_SERVER') or rcfilepath)
380         self.load()
381
382         # Verify that we want to log or not, if not the output will go to stdout
383         if self.options['logfile'] in ('None', 'False'):
384             self.options['logfile'] = False
385         # the same for the pidfile
386         if self.options['pidfile'] in ('None', 'False'):
387             self.options['pidfile'] = False
388
389         # if defined dont take the configfile value even if the defined value is None
390         keys = ['xmlrpc_interface', 'xmlrpc_port', 'longpolling_port',
391                 'db_name', 'db_user', 'db_password', 'db_host',
392                 'db_port', 'db_template', 'logfile', 'pidfile', 'smtp_port',
393                 'email_from', 'smtp_server', 'smtp_user', 'smtp_password',
394                 'db_maxconn', 'import_partial', 'addons_path',
395                 'xmlrpc', 'syslog', 'without_demo', 'timezone',
396                 'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs',
397                 'secure_cert_file', 'secure_pkey_file', 'dbfilter', 'log_handler', 'log_level', 'log_db'
398                 ]
399
400         for arg in keys:
401             # Copy the command-line argument (except the special case for log_handler, due to
402             # action=append requiring a real default, so we cannot use the my_default workaround)
403             if getattr(opt, arg) and getattr(opt, arg) != DEFAULT_LOG_HANDLER:
404                 self.options[arg] = getattr(opt, arg)
405             # ... or keep, but cast, the config file value.
406             elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
407                 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
408
409         if isinstance(self.options['log_handler'], basestring):
410             self.options['log_handler'] = self.options['log_handler'].split(',')
411
412         # if defined but None take the configfile value
413         keys = [
414             'language', 'translate_out', 'translate_in', 'overwrite_existing_translations',
415             'debug_mode', 'dev_mode', 'smtp_ssl', 'load_language',
416             'stop_after_init', 'logrotate', 'without_demo', 'xmlrpc', 'syslog',
417             'list_db', 'xmlrpcs', 'proxy_mode',
418             'test_file', 'test_enable', 'test_commit', 'test_report_directory',
419             'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
420             'data_dir',
421         ]
422
423         posix_keys = [
424             'workers',
425             'limit_memory_hard', 'limit_memory_soft',
426             'limit_time_cpu', 'limit_time_real', 'limit_request',
427         ]
428
429         if os.name == 'posix':
430             keys += posix_keys
431         else:
432             self.options.update(dict.fromkeys(posix_keys, None))
433
434         # Copy the command-line arguments...
435         for arg in keys:
436             if getattr(opt, arg) is not None:
437                 self.options[arg] = getattr(opt, arg)
438             # ... or keep, but cast, the config file value.
439             elif isinstance(self.options[arg], basestring) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
440                 self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
441
442         self.options['root_path'] = os.path.abspath(os.path.expanduser(os.path.expandvars(os.path.dirname(openerp.__file__))))
443         if not self.options['addons_path'] or self.options['addons_path']=='None':
444             base_addons = os.path.join(self.options['root_path'], 'addons')
445             main_addons = os.path.abspath(os.path.join(self.options['root_path'], '../addons'))
446             self.options['addons_path'] = '%s,%s' % (base_addons, main_addons)
447         else:
448             self.options['addons_path'] = ",".join(
449                     os.path.abspath(os.path.expanduser(os.path.expandvars(x)))
450                       for x in self.options['addons_path'].split(','))
451
452         self.options['init'] = opt.init and dict.fromkeys(opt.init.split(','), 1) or {}
453         self.options["demo"] = not opt.without_demo and self.options['init'] or {}
454         self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {}
455         self.options['translate_modules'] = opt.translate_modules and map(lambda m: m.strip(), opt.translate_modules.split(',')) or ['all']
456         self.options['translate_modules'].sort()
457
458         # TODO checking the type of the parameters should be done for every
459         # parameters, not just the timezone.
460         # The call to get_server_timezone() sets the timezone; this should
461         # probably done here.
462         if self.options['timezone']:
463             # Prevent the timezone to be True. (The config file parsing changes
464             # the string 'True' to the boolean value True. It would be probably
465             # be better to remove that conversion.)
466             die(not isinstance(self.options['timezone'], basestring),
467                 "Invalid timezone value in configuration or environment: %r.\n"
468                 "Please fix this in your configuration." %(self.options['timezone']))
469
470             # If an explicit TZ was provided in the config, make sure it is known
471             try:
472                 import pytz
473                 pytz.timezone(self.options['timezone'])
474             except pytz.UnknownTimeZoneError:
475                 die(True, "The specified timezone (%s) is invalid" % self.options['timezone'])
476             except:
477                 # If pytz is missing, don't check the provided TZ, it will be ignored anyway.
478                 pass
479
480         if opt.pg_path:
481             self.options['pg_path'] = opt.pg_path
482
483         if self.options.get('language', False):
484             if len(self.options['language']) > 5:
485                 raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE')
486
487         if not self.options['db_user']:
488             try:
489                 import getpass
490                 self.options['db_user'] = getpass.getuser()
491             except:
492                 self.options['db_user'] = None
493
494         die(not self.options['db_user'], 'ERROR: No user specified for the connection to the database')
495
496         if self.options['db_password']:
497             if sys.platform == 'win32' and not self.options['db_host']:
498                 self.options['db_host'] = 'localhost'
499             #if self.options['db_host']:
500             #    self._generate_pgpassfile()
501
502         if opt.save:
503             self.save()
504
505         openerp.conf.addons_paths = self.options['addons_path'].split(',')
506         if opt.server_wide_modules:
507             openerp.conf.server_wide_modules = map(lambda m: m.strip(), opt.server_wide_modules.split(','))
508         else:
509             openerp.conf.server_wide_modules = ['web','web_kanban']
510
511     def _generate_pgpassfile(self):
512         """
513         Generate the pgpass file with the parameters from the command line (db_host, db_user,
514         db_password)
515
516         Used because pg_dump and pg_restore can not accept the password on the command line.
517         """
518         is_win32 = sys.platform == 'win32'
519         if is_win32:
520             filename = os.path.join(os.environ['APPDATA'], 'pgpass.conf')
521         else:
522             filename = os.path.join(os.environ['HOME'], '.pgpass')
523
524         text_to_add = "%(db_host)s:*:*:%(db_user)s:%(db_password)s" % self.options
525
526         if os.path.exists(filename):
527             content = [x.strip() for x in file(filename, 'r').readlines()]
528             if text_to_add in content:
529                 return
530
531         fp = file(filename, 'a+')
532         fp.write(text_to_add + "\n")
533         fp.close()
534
535         if is_win32:
536             try:
537                 import _winreg
538             except ImportError:
539                 _winreg = None
540             x=_winreg.ConnectRegistry(None,_winreg.HKEY_LOCAL_MACHINE)
541             y = _winreg.OpenKey(x, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", 0,_winreg.KEY_ALL_ACCESS)
542             _winreg.SetValueEx(y,"PGPASSFILE", 0, _winreg.REG_EXPAND_SZ, filename )
543             _winreg.CloseKey(y)
544             _winreg.CloseKey(x)
545         else:
546             import stat
547             os.chmod(filename, stat.S_IRUSR + stat.S_IWUSR)
548
549     def _is_addons_path(self, path):
550         for f in os.listdir(path):
551             modpath = os.path.join(path, f)
552             if os.path.isdir(modpath):
553                 def hasfile(filename):
554                     return os.path.isfile(os.path.join(modpath, filename))
555                 if hasfile('__init__.py') and (hasfile('__openerp__.py') or hasfile('__terp__.py')):
556                     return True
557         return False
558
559     def _check_addons_path(self, option, opt, value, parser):
560         ad_paths = []
561         for path in value.split(','):
562             path = path.strip()
563             res = os.path.abspath(os.path.expanduser(path))
564             if not os.path.isdir(res):
565                 raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, path))
566             if not self._is_addons_path(res):
567                 raise optparse.OptionValueError("option %s: The addons-path %r does not seem to a be a valid Addons Directory!" % (opt, path))
568             ad_paths.append(res)
569
570         setattr(parser.values, option.dest, ",".join(ad_paths))
571
572     def load(self):
573         p = ConfigParser.ConfigParser()
574         try:
575             p.read([self.rcfile])
576             for (name,value) in p.items('options'):
577                 if value=='True' or value=='true':
578                     value = True
579                 if value=='False' or value=='false':
580                     value = False
581                 self.options[name] = value
582             #parse the other sections, as well
583             for sec in p.sections():
584                 if sec == 'options':
585                     continue
586                 if not self.misc.has_key(sec):
587                     self.misc[sec]= {}
588                 for (name, value) in p.items(sec):
589                     if value=='True' or value=='true':
590                         value = True
591                     if value=='False' or value=='false':
592                         value = False
593                     self.misc[sec][name] = value
594         except IOError:
595             pass
596         except ConfigParser.NoSectionError:
597             pass
598
599     def save(self):
600         p = ConfigParser.ConfigParser()
601         loglevelnames = dict(zip(self._LOGLEVELS.values(), self._LOGLEVELS.keys()))
602         p.add_section('options')
603         for opt in sorted(self.options.keys()):
604             if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'):
605                 continue
606             if opt in self.blacklist_for_save:
607                 continue
608             if opt in ('log_level',):
609                 p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt]))
610             else:
611                 p.set('options', opt, self.options[opt])
612
613         for sec in sorted(self.misc.keys()):
614             p.add_section(sec)
615             for opt in sorted(self.misc[sec].keys()):
616                 p.set(sec,opt,self.misc[sec][opt])
617
618         # try to create the directories and write the file
619         try:
620             rc_exists = os.path.exists(self.rcfile)
621             if not rc_exists and not os.path.exists(os.path.dirname(self.rcfile)):
622                 os.makedirs(os.path.dirname(self.rcfile))
623             try:
624                 p.write(file(self.rcfile, 'w'))
625                 if not rc_exists:
626                     os.chmod(self.rcfile, 0600)
627             except IOError:
628                 sys.stderr.write("ERROR: couldn't write the config file\n")
629
630         except OSError:
631             # what to do if impossible?
632             sys.stderr.write("ERROR: couldn't create the config directory\n")
633
634     def get(self, key, default=None):
635         return self.options.get(key, default)
636
637     def get_misc(self, sect, key, default=None):
638         return self.misc.get(sect,{}).get(key, default)
639
640     def __setitem__(self, key, value):
641         self.options[key] = value
642         if key in self.options and isinstance(self.options[key], basestring) and \
643                 key in self.casts and self.casts[key].type in optparse.Option.TYPE_CHECKER:
644             self.options[key] = optparse.Option.TYPE_CHECKER[self.casts[key].type](self.casts[key], key, self.options[key])
645
646     def __getitem__(self, key):
647         return self.options[key]
648
649     @property
650     def addons_data_dir(self):
651         d = os.path.join(self['data_dir'], 'addons', release.series)
652         if not os.path.exists(d):
653             os.makedirs(d, 0700)
654         else:
655             os.chmod(d, 0700)
656         return d
657
658     @property
659     def session_dir(self):
660         d = os.path.join(self['data_dir'], 'sessions')
661         if not os.path.exists(d):
662             os.makedirs(d, 0700)
663         else:
664             os.chmod(d, 0700)
665         return d
666
667     def filestore(self, dbname):
668         return os.path.join(self['data_dir'], 'filestore', dbname)
669
670 config = configmanager()
671
672
673 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: