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