[IMP] translation: added trans_update_res_ids to resolve the res_ids from ir_translation.
[odoo/odoo.git] / bin / openerp-server.py
index 55e2e85..a3f8ed8 100755 (executable)
@@ -1,32 +1,24 @@
-#!/usr/bin/python
-# -*- encoding: utf-8 -*-
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
 ##############################################################################
 #
 ##############################################################################
 #
-# Copyright (c) 2004-2008 Tiny SPRL (http://tiny.be) All Rights Reserved.
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
 #
 #
-# $Id$
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
 #
 #
-# WARNING: This program as such is intended to be used by professional
-# programmers who take the whole responsability of assessing all potential
-# consequences resulting from its eventual inadequacies and bugs
-# End users who are looking for a ready-to-use solution with commercial
-# garantees and support are strongly adviced to contract a Free Software
-# Service Company
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
 #
 #
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 #
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-###############################################################################
+##############################################################################
 
 """
 OpenERP - Server
 
 """
 OpenERP - Server
@@ -35,142 +27,108 @@ OpenERP is an ERP+CRM program for small and medium businesses.
 The whole source code is distributed under the terms of the
 GNU Public Licence.
 
 The whole source code is distributed under the terms of the
 GNU Public Licence.
 
-(c) 2003-TODAY, Fabien Pinckaers - Tiny sprl
+(c) 2003-TODAY, Fabien Pinckaers - OpenERP s.a.
 """
 """
-import release
-__author__ = release.author
-__version__ = release.version
 
 #----------------------------------------------------------
 # python imports
 #----------------------------------------------------------
 
 #----------------------------------------------------------
 # python imports
 #----------------------------------------------------------
-import sys, os, signal
-#----------------------------------------------------------
-# ubuntu 8.04 has obsoleted `pyxml` package and installs here.
-# the path needs to be updated before any `import xml`
-# TODO: remove PyXML dependencies, use lxml instead.
-#----------------------------------------------------------
-_oldxml = '/usr/lib/python%s/site-packages/oldxml' % sys.version[:3]
-if os.path.exists(_oldxml):
-    sys.path.append(_oldxml)
+import logging
+import os
+import signal
+import sys
+import threading
+import traceback
+
+import release
+__author__ = release.author
+__version__ = release.version
+
+if os.name == 'posix':
+    import pwd
+    # We DON't log this using the standard logger, because we might mess
+    # with the logfile's permissions. Just do a quick exit here.
+    if pwd.getpwuid(os.getuid())[0] == 'root' :
+        sys.stderr.write("Attempted to run OpenERP server as root. This is not good, aborting.\n")
+        sys.exit(1)
 
 #----------------------------------------------------------
 # get logger
 #----------------------------------------------------------
 import netsvc
 
 #----------------------------------------------------------
 # get logger
 #----------------------------------------------------------
 import netsvc
-
-netsvc.init_logger()
-
-logger = netsvc.Logger()
+logger = logging.getLogger('server')
 
 #-----------------------------------------------------------------------
 # import the tools module so that the commandline parameters are parsed
 #-----------------------------------------------------------------------
 import tools
 
 #-----------------------------------------------------------------------
 # import the tools module so that the commandline parameters are parsed
 #-----------------------------------------------------------------------
 import tools
-import time
-
-if sys.platform == 'win32':
-    import mx.DateTime
-    mx.DateTime.strptime = lambda x, y: mx.DateTime.mktime(time.strptime(x, y))
+logger.info("OpenERP version - %s", release.version)
+for name, value in [('addons_path', tools.config['addons_path']),
+                    ('database hostname', tools.config['db_host'] or 'localhost'),
+                    ('database port', tools.config['db_port'] or '5432'),
+                    ('database user', tools.config['db_user'])]:
+    logger.info("%s - %s", name, value)
+
+# Don't allow if the connection to PostgreSQL done by postgres user
+if tools.config['db_user'] == 'postgres':
+    logger.error("Connecting to the database as 'postgres' user is forbidden, as it present major security issues. Shutting down.")
+    sys.exit(1)
 
 
-#os.chdir(tools.file_path_root)
+import time
 
 #----------------------------------------------------------
 # init net service
 #----------------------------------------------------------
 
 #----------------------------------------------------------
 # init net service
 #----------------------------------------------------------
-logger.notifyChannel("objects", netsvc.LOG_INFO, 'initialising distributed objects services')
-
-dispatcher = netsvc.Dispatcher()
-dispatcher.monitor(signal.SIGINT)
+logger.info('initialising distributed objects services')
 
 #---------------------------------------------------------------
 # connect to the database and initialize it with base if needed
 #---------------------------------------------------------------
 
 #---------------------------------------------------------------
 # connect to the database and initialize it with base if needed
 #---------------------------------------------------------------
-logger.notifyChannel("init", netsvc.LOG_INFO, 'connecting to database')
-
-import psycopg
 import pooler
 
 import pooler
 
-# try to connect to the database
-try:
-#   pooler.init()
-    pass
-except psycopg.OperationalError, err:
-    logger.notifyChannel("init", netsvc.LOG_ERROR, "could not connect to database '%s'!" % (tools.config["db_name"],))
-
-    msg = str(err).replace("FATAL:", "").strip()
-    db_msg = "database \"%s\" does not exist" % (tools.config["db_name"],)
-
-    # Note: this is ugly but since psycopg only uses one exception for all errors
-    # I don't think it's possible to do differently
-    if msg == db_msg:
-        print """
-    this database does not exist
-
-You need to create it using the command:
+#----------------------------------------------------------
+# import basic modules
+#----------------------------------------------------------
+import osv
+import workflow
+import report
+import service
 
 
-    createdb --encoding=UNICODE '%s'
+#----------------------------------------------------------
+# import addons
+#----------------------------------------------------------
 
 
-When you run openerp-server for the first time it will initialise the
-database. You may force this behaviour at a later time by using the command:
+import addons
 
 
-    ./openerp-server --init=all
+#----------------------------------------------------------
+# Load and update databases if requested
+#----------------------------------------------------------
 
 
-Two accounts will be created by default:
-    1. login: admin      password : admin
-    2. login: demo       password : demo
+import service.http_server
 
 
-""" % (tools.config["db_name"])
-    else:
-        print "\n    "+msg+"\n"
-    sys.exit(1)
+if not ( tools.config["stop_after_init"] or \
+    tools.config["translate_in"] or \
+    tools.config["translate_out"] ):
+    service.http_server.init_servers()
+    service.http_server.init_xmlrpc()
+    service.http_server.init_static_http()
 
 
-db_name = tools.config["db_name"]
-
-# test whether it is needed to initialize the db (the db is empty)
-try:
-    cr = pooler.get_db_only(db_name).cursor()
-except psycopg.OperationalError:
-    logger.notifyChannel("init", netsvc.LOG_INFO, "could not connect to database '%s'!" % db_name,)
-    cr = None
-if cr:
-    cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_ui_menu'")
-    if len(cr.fetchall())==0:
-#if False:
-        logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
-        tools.init_db(cr)
-        # in that case, force --init=all
-        tools.config["init"]["all"] = 1
-        tools.config['update']['all'] = 1
-        if not tools.config['without_demo']:
-            tools.config["demo"]['all'] = 1
-    cr.close()
+    import service.netrpc_server
+    service.netrpc_server.init_servers()
 
 
-#----------------------------------------------------------
-# launch modules install/upgrade/removes if needed
-#----------------------------------------------------------
-if tools.config['upgrade']:
-    print 'Upgrading new modules...'
-    import tools.upgrade
-    (toinit, toupdate) = tools.upgrade.upgrade()
-    for m in toinit:
-        tools.config['init'][m] = 1
-    for m in toupdate:
-        tools.config['update'][m] = 1
+if tools.config['db_name']:
+    for dbname in tools.config['db_name'].split(','):
+        db,pool = pooler.get_db_and_pool(dbname, update_module=tools.config['init'] or tools.config['update'], pooljobs=False)
+        cr = db.cursor()
 
 
-#----------------------------------------------------------
-# import basic modules
-#----------------------------------------------------------
-import osv, workflow, report, service
+        if tools.config["test_file"]:
+            logger.info('loading test file %s', tools.config["test_file"])
+            tools.convert_yaml_import(cr, 'base', file(tools.config["test_file"]), {}, 'test', True)
+            cr.rollback()
 
 
-#----------------------------------------------------------
-# import addons
-#----------------------------------------------------------
-import addons
+        pool.get('ir.cron')._poolJobs(db.dbname)
 
 
-addons.register_classes()
-if tools.config['init'] or tools.config['update']:
-    pooler.get_db_and_pool(tools.config['db_name'], update_module=True)
+        cr.close()
 
 #----------------------------------------------------------
 # translation stuff
 
 #----------------------------------------------------------
 # translation stuff
@@ -182,18 +140,30 @@ if tools.config["translate_out"]:
         msg = "language %s" % (tools.config["language"],)
     else:
         msg = "new language"
         msg = "language %s" % (tools.config["language"],)
     else:
         msg = "new language"
-    logger.notifyChannel("init", netsvc.LOG_INFO, 'writing translation file for %s to %s' % (msg, tools.config["translate_out"]))
+    logger.info('writing translation file for %s to %s', msg, tools.config["translate_out"])
 
     fileformat = os.path.splitext(tools.config["translate_out"])[-1][1:].lower()
     buf = file(tools.config["translate_out"], "w")
 
     fileformat = os.path.splitext(tools.config["translate_out"])[-1][1:].lower()
     buf = file(tools.config["translate_out"], "w")
-    tools.trans_export(tools.config["language"], tools.config["translate_modules"], buf, fileformat)
+    dbname = tools.config['db_name']
+    cr = pooler.get_db(dbname).cursor()
+    tools.trans_export(tools.config["language"], tools.config["translate_modules"] or ["all"], buf, fileformat, cr)
+    cr.close()
     buf.close()
 
     buf.close()
 
-    logger.notifyChannel("init", netsvc.LOG_INFO, 'translation file written succesfully')
+    logger.info('translation file written successfully')
     sys.exit(0)
 
 if tools.config["translate_in"]:
     sys.exit(0)
 
 if tools.config["translate_in"]:
-    tools.trans_load(tools.config["db_name"], tools.config["translate_in"], tools.config["language"])
+    context = {'overwrite': tools.config["overwrite_existing_translations"]}
+    dbname = tools.config['db_name']
+    cr = pooler.get_db(dbname).cursor()
+    tools.trans_load(cr,
+                     tools.config["translate_in"], 
+                     tools.config["language"],
+                     context=context)
+    tools.trans_update_res_ids(cr)
+    cr.commit()
+    cr.close()
     sys.exit(0)
 
 #----------------------------------------------------------------------------------
     sys.exit(0)
 
 #----------------------------------------------------------------------------------
@@ -204,77 +174,82 @@ if tools.config["stop_after_init"]:
 
 
 #----------------------------------------------------------
 
 
 #----------------------------------------------------------
-# Launch Server
+# Launch Servers
 #----------------------------------------------------------
 
 #----------------------------------------------------------
 
-if tools.config['xmlrpc']:
-    try:
-        port = int(tools.config["port"])
-    except Exception:
-        logger.notifyChannel("init", netsvc.LOG_ERROR, "invalid port '%s'!" % (tools.config["port"],))
-        sys.exit(1)
-    interface = tools.config["interface"]
-    secure = tools.config["secure"]
-
-    httpd = netsvc.HttpDaemon(interface, port, secure)
-
-    if tools.config["xmlrpc"]:
-        xml_gw = netsvc.xmlrpc.RpcGateway('web-services')
-        httpd.attach("/xmlrpc", xml_gw)
-        logger.notifyChannel("web-services", netsvc.LOG_INFO,
-                "starting XML-RPC" + \
-                        (tools.config['secure'] and ' Secure' or '') + \
-                        " services, port " + str(port))
-
-    #
-    #if tools.config["soap"]:
-    #   soap_gw = netsvc.xmlrpc.RpcGateway('web-services')
-    #   httpd.attach("/soap", soap_gw )
-    #   logger.notifyChannel("web-services", netsvc.LOG_INFO, 'starting SOAP services, port '+str(port))
-    #
-
-if tools.config['netrpc']:
-    try:
-        netport = int(tools.config["netport"])
-    except Exception:
-        logger.notifyChannel("init", netsvc.LOG_ERROR, "invalid port '%s'!" % (tools.config["netport"],))
-        sys.exit(1)
-    netinterface = tools.config["netinterface"]
+LST_SIGNALS = ['SIGINT', 'SIGTERM']
 
 
-    tinySocket = netsvc.TinySocketServerThread(netinterface, netport, False)
-    logger.notifyChannel("web-services", netsvc.LOG_INFO, "starting netrpc service, port "+str(netport))
+SIGNALS = dict(
+    [(getattr(signal, sign), sign) for sign in LST_SIGNALS]
+)
 
 
+netsvc.quit_signals_received = 0
 
 def handler(signum, frame):
 
 def handler(signum, frame):
-    from tools import config
-    if tools.config['netrpc']:
-        tinySocket.stop()
-    if tools.config['xmlrpc']:
-        httpd.stop()
+    """
+    :param signum: the signal number
+    :param frame: the interrupted stack frame or None
+    """
+    netsvc.quit_signals_received += 1
+    if netsvc.quit_signals_received > 1:
+        sys.stderr.write("Forced shutdown.\n")
+        os._exit(0)
+
+def dumpstacks(signum, frame):
+    # code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
+    # modified for python 2.5 compatibility
+    thread_map = dict(threading._active, **threading._limbo)
+    id2name = dict([(threadId, thread.getName()) for threadId, thread in thread_map.items()])
+    code = []
+    for threadId, stack in sys._current_frames().items():
+        code.append("\n# Thread: %s(%d)" % (id2name[threadId], threadId))
+        for filename, lineno, name, line in traceback.extract_stack(stack):
+            code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
+            if line:
+                code.append("  %s" % (line.strip()))
+    logging.getLogger('dumpstacks').info("\n".join(code))
+
+for signum in SIGNALS:
+    signal.signal(signum, handler)
+
+if os.name == 'posix':
+    signal.signal(signal.SIGQUIT, dumpstacks)
+
+def quit():
     netsvc.Agent.quit()
     netsvc.Agent.quit()
-    if config['pidfile']:
-        os.unlink(config['pidfile'])
+    netsvc.Server.quitAll()
+    if tools.config['pidfile']:
+        os.unlink(tools.config['pidfile'])
+    logger = logging.getLogger('shutdown')
+    logger.info("Initiating OpenERP Server shutdown")
+    logger.info("Hit CTRL-C again or send a second signal to immediately terminate the server...")
+    logging.shutdown()
+
+    # manually join() all threads before calling sys.exit() to allow a second signal
+    # to trigger _force_quit() in case some non-daemon threads won't exit cleanly.
+    # threading.Thread.join() should not mask signals (at least in python 2.5)
+    for thread in threading.enumerate():
+        if thread != threading.currentThread() and not thread.isDaemon():
+            while thread.isAlive():
+                # need a busyloop here as thread.join() masks signals
+                # and would present the forced shutdown
+                thread.join(0.05)
+                time.sleep(0.05)
     sys.exit(0)
 
     sys.exit(0)
 
-from tools import config
-if config['pidfile']:
-    fd = open(config['pidfile'], 'w')
+if tools.config['pidfile']:
+    fd = open(tools.config['pidfile'], 'w')
     pidtext = "%d" % (os.getpid())
     fd.write(pidtext)
     fd.close()
 
     pidtext = "%d" % (os.getpid())
     fd.write(pidtext)
     fd.close()
 
-signal.signal(signal.SIGINT, handler)
-signal.signal(signal.SIGTERM, handler)
+netsvc.Server.startAll()
 
 
-logger.notifyChannel("web-services", netsvc.LOG_INFO, 'the server is running, waiting for connections...')
-if tools.config['netrpc']:
-    tinySocket.start()
-if tools.config['xmlrpc']:
-    httpd.start()
-#dispatcher.run()
+logger.info('OpenERP server is running, waiting for connections...')
 
 
-while True:
-    time.sleep(1)
+while netsvc.quit_signals_received == 0:
+    time.sleep(60)
 
 
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+quit()
 
 
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: