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