[IMP] Make wizard work
[odoo/odoo.git] / openerp / service / web_services.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21 from __future__ import with_statement
22 import contextlib
23 import base64
24 import locale
25 import logging
26 import os
27 import platform
28 import security
29 import sys
30 import thread
31 import threading
32 import time
33 import traceback
34 from cStringIO import StringIO
35 from openerp.tools.translate import _
36 import openerp.netsvc as netsvc
37 import openerp.pooler as pooler
38 import openerp.release as release
39 import openerp.sql_db as sql_db
40 import openerp.tools as tools
41 import openerp.modules
42 import openerp.exceptions
43 from openerp.service import http_server
44 from openerp import SUPERUSER_ID
45
46 #.apidoc title: Exported Service methods
47 #.apidoc module-mods: member-order: bysource
48
49 """ This python module defines the RPC methods available to remote clients.
50
51     Each 'Export Service' is a group of 'methods', which in turn are RPC
52     procedures to be called. Each method has its own arguments footprint.
53 """
54
55 _logger = logging.getLogger(__name__)
56
57 RPC_VERSION_1 = {'server_version': '6.1', 'protocol_version': 1}
58
59 # This should be moved to openerp.modules.db, along side initialize().
60 def _initialize_db(serv, id, db_name, demo, lang, user_password):
61     cr = None
62     try:
63         serv.actions[id]['progress'] = 0
64         cr = sql_db.db_connect(db_name).cursor()
65         openerp.modules.db.initialize(cr) # TODO this should be removed as it is done by pooler.restart_pool.
66         tools.config['lang'] = lang
67         cr.commit()
68         cr.close()
69
70         pool = pooler.restart_pool(db_name, demo, serv.actions[id],
71                                    update_module=True)[1]
72
73         cr = sql_db.db_connect(db_name).cursor()
74
75         if lang:
76             modobj = pool.get('ir.module.module')
77             mids = modobj.search(cr, SUPERUSER_ID, [('state', '=', 'installed')])
78             modobj.update_translations(cr, SUPERUSER_ID, mids, lang)
79
80         cr.execute('UPDATE res_users SET password=%s, lang=%s, active=True WHERE login=%s', (
81             user_password, lang, 'admin'))
82         cr.execute('SELECT login, password ' \
83                    ' FROM res_users ' \
84                    ' ORDER BY login')
85         serv.actions[id].update(users=cr.dictfetchall(), clean=True)
86         cr.commit()
87         cr.close()
88     except Exception, e:
89         serv.actions[id].update(clean=False, exception=e)
90         _logger.exception('CREATE DATABASE failed:')
91         serv.actions[id]['traceback'] = traceback.format_exc()
92         if cr:
93             cr.close()
94
95 class db(netsvc.ExportService):
96     def __init__(self, name="db"):
97         netsvc.ExportService.__init__(self, name)
98         self.actions = {}
99         self.id = 0
100         self.id_protect = threading.Semaphore()
101
102     def dispatch(self, method, params):
103         if method in [ 'create', 'get_progress', 'drop', 'dump',
104             'restore', 'rename',
105             'change_admin_password', 'migrate_databases',
106             'create_database', 'duplicate_database' ]:
107             passwd = params[0]
108             params = params[1:]
109             security.check_super(passwd)
110         elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
111             # params = params
112             # No security check for these methods
113             pass
114         else:
115             raise KeyError("Method not found: %s" % method)
116         fn = getattr(self, 'exp_'+method)
117         return fn(*params)
118
119     def _create_empty_database(self, name):
120         db = sql_db.db_connect('postgres')
121         cr = db.cursor()
122         chosen_template = tools.config['db_template']
123         try:
124             cr.autocommit(True) # avoid transaction block
125             cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (name, chosen_template))
126         finally:
127             cr.close()
128
129     def exp_create(self, db_name, demo, lang, user_password='admin'):
130         self.id_protect.acquire()
131         self.id += 1
132         id = self.id
133         self.id_protect.release()
134
135         self.actions[id] = {'clean': False}
136
137         self._create_empty_database(db_name)
138
139         _logger.info('CREATE DATABASE %s', db_name.lower())
140         create_thread = threading.Thread(target=_initialize_db,
141                 args=(self, id, db_name, demo, lang, user_password))
142         create_thread.start()
143         self.actions[id]['thread'] = create_thread
144         return id
145
146     def exp_create_database(self, db_name, demo, lang, user_password='admin'):
147         """ Similar to exp_create but blocking."""
148         self.id_protect.acquire()
149         self.id += 1
150         id = self.id
151         self.id_protect.release()
152
153         self.actions[id] = {'clean': False}
154
155         _logger.info('Create database `%s`.', db_name)
156         self._create_empty_database(db_name)
157         _initialize_db(self, id, db_name, demo, lang, user_password)
158         return True
159
160     def exp_duplicate_database(self, db_original_name, db_name):
161         _logger.info('Duplicate database `%s` to `%s`.', db_original_name, db_name)
162         sql_db.close_db(db_original_name)
163         db = sql_db.db_connect('postgres')
164         cr = db.cursor()
165         try:
166             cr.autocommit(True) # avoid transaction block
167             cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "%s" """ % (db_name, db_original_name))
168         finally:
169             cr.close()
170         return True
171
172     def exp_get_progress(self, id):
173         if self.actions[id]['thread'].isAlive():
174 #           return openerp.modules.init_progress[db_name]
175             return min(self.actions[id].get('progress', 0),0.95), []
176         else:
177             clean = self.actions[id]['clean']
178             if clean:
179                 users = self.actions[id]['users']
180                 self.actions.pop(id)
181                 return 1.0, users
182             else:
183                 e = self.actions[id]['exception'] # TODO this seems wrong: actions[id]['traceback'] is set, but not 'exception'.
184                 self.actions.pop(id)
185                 raise Exception, e
186
187     def exp_drop(self, db_name):
188         if not self.exp_db_exist(db_name):
189             return False
190         openerp.modules.registry.RegistryManager.delete(db_name)
191         sql_db.close_db(db_name)
192
193         db = sql_db.db_connect('postgres')
194         cr = db.cursor()
195         cr.autocommit(True) # avoid transaction block
196         try:
197             # Try to terminate all other connections that might prevent
198             # dropping the database
199             try:
200
201                 # PostgreSQL 9.2 renamed pg_stat_activity.procpid to pid:
202                 # http://www.postgresql.org/docs/9.2/static/release-9-2.html#AEN110389
203                 pid_col = 'pid' if cr._cnx.server_version >= 90200 else 'procpid'
204
205                 cr.execute("""SELECT pg_terminate_backend(%(pid_col)s)
206                               FROM pg_stat_activity
207                               WHERE datname = %%s AND 
208                                     %(pid_col)s != pg_backend_pid()""" % {'pid_col': pid_col},
209                            (db_name,))
210             except Exception:
211                 pass
212
213             try:
214                 cr.execute('DROP DATABASE "%s"' % db_name)
215             except Exception, e:
216                 _logger.error('DROP DB: %s failed:\n%s', db_name, e)
217                 raise Exception("Couldn't drop database %s: %s" % (db_name, e))
218             else:
219                 _logger.info('DROP DB: %s', db_name)
220         finally:
221             cr.close()
222         return True
223
224     @contextlib.contextmanager
225     def _set_pg_password_in_environment(self):
226         """ On Win32, pg_dump (and pg_restore) require that
227         :envvar:`PGPASSWORD` be set
228
229         This context management method handles setting
230         :envvar:`PGPASSWORD` iif win32 and the envvar is not already
231         set, and removing it afterwards.
232         """
233         if os.name != 'nt' or os.environ.get('PGPASSWORD'):
234             yield
235         else:
236             os.environ['PGPASSWORD'] = tools.config['db_password']
237             try:
238                 yield
239             finally:
240                 del os.environ['PGPASSWORD']
241
242
243     def exp_dump(self, db_name):
244         logger = logging.getLogger('openerp.service.web_services.db.dump')
245         with self._set_pg_password_in_environment():
246             cmd = ['pg_dump', '--format=c', '--no-owner']
247             if tools.config['db_user']:
248                 cmd.append('--username=' + tools.config['db_user'])
249             if tools.config['db_host']:
250                 cmd.append('--host=' + tools.config['db_host'])
251             if tools.config['db_port']:
252                 cmd.append('--port=' + str(tools.config['db_port']))
253             cmd.append(db_name)
254     
255             stdin, stdout = tools.exec_pg_command_pipe(*tuple(cmd))
256             stdin.close()
257             data = stdout.read()
258             res = stdout.close()
259     
260             if not data or res:
261                 logger.error(
262                         'DUMP DB: %s failed! Please verify the configuration of the database password on the server. '
263                         'It should be provided as a -w <PASSWD> command-line option, or as `db_password` in the '
264                         'server configuration file.\n %s', db_name, data)
265                 raise Exception, "Couldn't dump database"
266             logger.info('DUMP DB successful: %s', db_name)
267     
268             return base64.encodestring(data)
269
270     def exp_restore(self, db_name, data):
271         logger = logging.getLogger('openerp.service.web_services.db.restore')
272         with self._set_pg_password_in_environment():
273             if self.exp_db_exist(db_name):
274                 logger.warning('RESTORE DB: %s already exists', db_name)
275                 raise Exception, "Database already exists"
276
277             self._create_empty_database(db_name)
278
279             cmd = ['pg_restore', '--no-owner']
280             if tools.config['db_user']:
281                 cmd.append('--username=' + tools.config['db_user'])
282             if tools.config['db_host']:
283                 cmd.append('--host=' + tools.config['db_host'])
284             if tools.config['db_port']:
285                 cmd.append('--port=' + str(tools.config['db_port']))
286             cmd.append('--dbname=' + db_name)
287             args2 = tuple(cmd)
288
289             buf=base64.decodestring(data)
290             if os.name == "nt":
291                 tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
292                 file(tmpfile, 'wb').write(buf)
293                 args2=list(args2)
294                 args2.append(tmpfile)
295                 args2=tuple(args2)
296             stdin, stdout = tools.exec_pg_command_pipe(*args2)
297             if not os.name == "nt":
298                 stdin.write(base64.decodestring(data))
299             stdin.close()
300             res = stdout.close()
301             if res:
302                 raise Exception, "Couldn't restore database"
303             logger.info('RESTORE DB: %s', db_name)
304
305             return True
306
307     def exp_rename(self, old_name, new_name):
308         openerp.modules.registry.RegistryManager.delete(old_name)
309         sql_db.close_db(old_name)
310
311         db = sql_db.db_connect('postgres')
312         cr = db.cursor()
313         cr.autocommit(True) # avoid transaction block
314         try:
315             try:
316                 cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
317             except Exception, e:
318                 _logger.error('RENAME DB: %s -> %s failed:\n%s', old_name, new_name, e)
319                 raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
320             else:
321                 fs = os.path.join(tools.config['root_path'], 'filestore')
322                 if os.path.exists(os.path.join(fs, old_name)):
323                     os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name))
324
325                 _logger.info('RENAME DB: %s -> %s', old_name, new_name)
326         finally:
327             cr.close()
328         return True
329
330     def exp_db_exist(self, db_name):
331         ## Not True: in fact, check if connection to database is possible. The database may exists
332         return bool(sql_db.db_connect(db_name))
333
334     def exp_list(self, document=False):
335         if not tools.config['list_db'] and not document:
336             raise openerp.exceptions.AccessDenied()
337         chosen_template = tools.config['db_template']
338         templates_list = tuple(set(['template0', 'template1', 'postgres', chosen_template]))
339         db = sql_db.db_connect('postgres')
340         cr = db.cursor()
341         try:
342             try:
343                 db_user = tools.config["db_user"]
344                 if not db_user and os.name == 'posix':
345                     import pwd
346                     db_user = pwd.getpwuid(os.getuid())[0]
347                 if not db_user:
348                     cr.execute("select usename from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],))
349                     res = cr.fetchone()
350                     db_user = res and str(res[0])
351                 if db_user:
352                     cr.execute("select datname from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in %s order by datname", (db_user, templates_list))
353                 else:
354                     cr.execute("select datname from pg_database where datname not in %s order by datname", (templates_list,))
355                 res = [str(name) for (name,) in cr.fetchall()]
356             except Exception:
357                 res = []
358         finally:
359             cr.close()
360         res.sort()
361         return res
362
363     def exp_change_admin_password(self, new_password):
364         tools.config['admin_passwd'] = new_password
365         tools.config.save()
366         return True
367
368     def exp_list_lang(self):
369         return tools.scan_languages()
370
371     def exp_server_version(self):
372         """ Return the version of the server
373             Used by the client to verify the compatibility with its own version
374         """
375         return release.version
376
377     def exp_migrate_databases(self,databases):
378
379         from openerp.osv.orm import except_orm
380         from openerp.osv.osv import except_osv
381
382         for db in databases:
383             try:
384                 _logger.info('migrate database %s', db)
385                 tools.config['update']['base'] = True
386                 pooler.restart_pool(db, force_demo=False, update_module=True)
387             except except_orm, inst:
388                 netsvc.abort_response(1, inst.name, 'warning', inst.value)
389             except except_osv, inst:
390                 netsvc.abort_response(1, inst.name, 'warning', inst.value)
391             except Exception:
392                 _logger.exception('Exception in migrate_databases:')
393                 raise
394         return True
395
396 class common(netsvc.ExportService):
397
398     def __init__(self,name="common"):
399         netsvc.ExportService.__init__(self,name)
400
401     def dispatch(self, method, params):
402         if method in ['login', 'about', 'timezone_get', 'get_server_environment',
403                       'login_message','get_stats', 'check_connectivity',
404                       'list_http_services', 'version', 'authenticate']:
405             pass
406         elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel', 'get_os_time', 'get_sqlcount']:
407             passwd = params[0]
408             params = params[1:]
409             security.check_super(passwd)
410         else:
411             raise Exception("Method not found: %s" % method)
412
413         fn = getattr(self, 'exp_'+method)
414         return fn(*params)
415
416     def exp_login(self, db, login, password):
417         # TODO: legacy indirection through 'security', should use directly
418         # the res.users model
419         res = security.login(db, login, password)
420         msg = res and 'successful login' or 'bad login or password'
421         _logger.info("%s from '%s' using database '%s'", msg, login, db.lower())
422         return res or False
423
424     def exp_authenticate(self, db, login, password, user_agent_env):
425         res_users = pooler.get_pool(db).get('res.users')
426         return res_users.authenticate(db, login, password, user_agent_env)
427
428     def exp_version(self):
429         return RPC_VERSION_1
430
431     def exp_about(self, extended=False):
432         """Return information about the OpenERP Server.
433
434         @param extended: if True then return version info
435         @return string if extended is False else tuple
436         """
437
438         info = _('''
439
440 OpenERP is an ERP+CRM program for small and medium businesses.
441
442 The whole source code is distributed under the terms of the
443 GNU Public Licence.
444
445 (c) 2003-TODAY - OpenERP SA''')
446
447         if extended:
448             return info, release.version
449         return info
450
451     def exp_timezone_get(self, db, login, password):
452         return tools.misc.get_server_timezone()
453
454     def exp_get_available_updates(self, contract_id, contract_password):
455         import openerp.tools.maintenance as tm
456         try:
457             rc = tm.remote_contract(contract_id, contract_password)
458             if not rc.id:
459                 raise tm.RemoteContractException('This contract does not exist or is not active')
460
461             return rc.get_available_updates(rc.id, openerp.modules.get_modules_with_version())
462
463         except tm.RemoteContractException, e:
464             netsvc.abort_response(1, 'Migration Error', 'warning', str(e))
465
466
467     def exp_get_migration_scripts(self, contract_id, contract_password):
468         import openerp.tools.maintenance as tm
469         try:
470             rc = tm.remote_contract(contract_id, contract_password)
471             if not rc.id:
472                 raise tm.RemoteContractException('This contract does not exist or is not active')
473             if rc.status != 'full':
474                 raise tm.RemoteContractException('Can not get updates for a partial contract')
475
476             _logger.info('starting migration with contract %s', rc.name)
477
478             zips = rc.retrieve_updates(rc.id, openerp.modules.get_modules_with_version())
479
480             from shutil import rmtree, copytree, copy
481
482             backup_directory = os.path.join(tools.config['root_path'], 'backup', time.strftime('%Y-%m-%d-%H-%M'))
483             if zips and not os.path.isdir(backup_directory):
484                 _logger.info('create a new backup directory to store the old modules: %s', backup_directory)
485                 os.makedirs(backup_directory)
486
487             for module in zips:
488                 _logger.info('upgrade module %s', module)
489                 mp = openerp.modules.get_module_path(module)
490                 if mp:
491                     if os.path.isdir(mp):
492                         copytree(mp, os.path.join(backup_directory, module))
493                         if os.path.islink(mp):
494                             os.unlink(mp)
495                         else:
496                             rmtree(mp)
497                     else:
498                         copy(mp + 'zip', backup_directory)
499                         os.unlink(mp + '.zip')
500
501                 try:
502                     try:
503                         base64_decoded = base64.decodestring(zips[module])
504                     except Exception:
505                         _logger.error('unable to read the module %s', module)
506                         raise
507
508                     zip_contents = StringIO(base64_decoded)
509                     zip_contents.seek(0)
510                     try:
511                         try:
512                             tools.extract_zip_file(zip_contents, tools.config['addons_path'] )
513                         except Exception:
514                             _logger.error('unable to extract the module %s', module)
515                             rmtree(module)
516                             raise
517                     finally:
518                         zip_contents.close()
519                 except Exception:
520                     _logger.error('restore the previous version of the module %s', module)
521                     nmp = os.path.join(backup_directory, module)
522                     if os.path.isdir(nmp):
523                         copytree(nmp, tools.config['addons_path'])
524                     else:
525                         copy(nmp+'.zip', tools.config['addons_path'])
526                     raise
527
528             return True
529         except tm.RemoteContractException, e:
530             netsvc.abort_response(1, 'Migration Error', 'warning', str(e))
531         except Exception, e:
532             _logger.exception('Exception in get_migration_script:')
533             raise
534
535     def exp_get_server_environment(self):
536         os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] )
537         if not os_lang:
538             os_lang = 'NOT SET'
539         environment = '\nEnvironment Information : \n' \
540                      'System : %s\n' \
541                      'OS Name : %s\n' \
542                      %(platform.platform(), platform.os.name)
543         if os.name == 'posix':
544             if platform.system() == 'Linux':
545                 lsbinfo = os.popen('lsb_release -a').read()
546                 environment += '%s'% lsbinfo
547             else:
548                 environment += 'Your System is not lsb compliant\n'
549         environment += 'Operating System Release : %s\n' \
550                     'Operating System Version : %s\n' \
551                     'Operating System Architecture : %s\n' \
552                     'Operating System Locale : %s\n'\
553                     'Python Version : %s\n'\
554                     'OpenERP-Server Version : %s'\
555                     %(platform.release(), platform.version(), platform.architecture()[0],
556                       os_lang, platform.python_version(),release.version)
557         return environment
558
559     def exp_login_message(self):
560         return tools.config.get('login_message', False)
561
562     def exp_set_loglevel(self, loglevel, logger=None):
563         # TODO Previously, the level was set on the now deprecated
564         # `openerp.netsvc.Logger` class.
565         return True
566
567     def exp_get_stats(self):
568         res = "OpenERP server: %d threads\n" % threading.active_count()
569         res += netsvc.Server.allStats()
570         return res
571
572     def exp_list_http_services(self):
573         return http_server.list_http_services()
574
575     def exp_check_connectivity(self):
576         return bool(sql_db.db_connect('postgres'))
577
578     def exp_get_os_time(self):
579         return os.times()
580
581     def exp_get_sqlcount(self):
582         if not logging.getLogger('openerp.sql_db').isEnabledFor(logging.DEBUG):
583             _logger.warning("Counters of SQL will not be reliable unless logger openerp.sql_db is set to level DEBUG or higer.")
584         return sql_db.sql_counter
585
586
587 class objects_proxy(netsvc.ExportService):
588     def __init__(self, name="object"):
589         netsvc.ExportService.__init__(self,name)
590
591     def dispatch(self, method, params):
592         (db, uid, passwd ) = params[0:3]
593         threading.current_thread().uid = uid
594         params = params[3:]
595         if method == 'obj_list':
596             raise NameError("obj_list has been discontinued via RPC as of 6.0, please query ir.model directly!")
597         if method not in ['execute', 'execute_kw', 'exec_workflow']:
598             raise NameError("Method not available %s" % method)
599         security.check(db,uid,passwd)
600         assert openerp.osv.osv.service, "The object_proxy class must be started with start_object_proxy."
601         openerp.modules.registry.RegistryManager.check_registry_signaling(db)
602         fn = getattr(openerp.osv.osv.service, method)
603         res = fn(db, uid, *params)
604         openerp.modules.registry.RegistryManager.signal_caches_change(db)
605         return res
606
607
608 #
609 # TODO: set a maximum report number per user to avoid DOS attacks
610 #
611 # Report state:
612 #     False -> True
613 #
614
615 class report_spool(netsvc.ExportService):
616     def __init__(self, name='report'):
617         netsvc.ExportService.__init__(self, name)
618         self._reports = {}
619         self.id = 0
620         self.id_protect = threading.Semaphore()
621
622     def dispatch(self, method, params):
623         (db, uid, passwd ) = params[0:3]
624         threading.current_thread().uid = uid
625         params = params[3:]
626         if method not in ['report', 'report_get', 'render_report']:
627             raise KeyError("Method not supported %s" % method)
628         security.check(db,uid,passwd)
629         openerp.modules.registry.RegistryManager.check_registry_signaling(db)
630         fn = getattr(self, 'exp_' + method)
631         res = fn(db, uid, *params)
632         openerp.modules.registry.RegistryManager.signal_caches_change(db)
633         return res
634
635     def exp_render_report(self, db, uid, object, ids, datas=None, context=None):
636         if not datas:
637             datas={}
638         if not context:
639             context={}
640
641         self.id_protect.acquire()
642         self.id += 1
643         id = self.id
644         self.id_protect.release()
645
646         self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
647
648         cr = pooler.get_db(db).cursor()
649         try:
650             obj = netsvc.LocalService('report.'+object)
651             (result, format) = obj.create(cr, uid, ids, datas, context)
652             if not result:
653                 tb = sys.exc_info()
654                 self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
655             self._reports[id]['result'] = result
656             self._reports[id]['format'] = format
657             self._reports[id]['state'] = True
658         except Exception, exception:
659
660             _logger.exception('Exception: %s\n', exception)
661             if hasattr(exception, 'name') and hasattr(exception, 'value'):
662                 self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
663             else:
664                 tb = sys.exc_info()
665                 self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
666             self._reports[id]['state'] = True
667         cr.commit()
668         cr.close()
669
670         return self._check_report(id)
671
672     def exp_report(self, db, uid, object, ids, datas=None, context=None):
673         if not datas:
674             datas={}
675         if not context:
676             context={}
677
678         self.id_protect.acquire()
679         self.id += 1
680         id = self.id
681         self.id_protect.release()
682
683         self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
684
685         def go(id, uid, ids, datas, context):
686             cr = pooler.get_db(db).cursor()
687             try:
688                 obj = netsvc.LocalService('report.'+object)
689                 (result, format) = obj.create(cr, uid, ids, datas, context)
690                 if not result:
691                     tb = sys.exc_info()
692                     self._reports[id]['exception'] = openerp.exceptions.DeferredException('RML is not available at specified location or not enough data to print!', tb)
693                 self._reports[id]['result'] = result
694                 self._reports[id]['format'] = format
695                 self._reports[id]['state'] = True
696             except Exception, exception:
697                 _logger.exception('Exception: %s\n', exception)
698                 if hasattr(exception, 'name') and hasattr(exception, 'value'):
699                     self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.ustr(exception.name), tools.ustr(exception.value))
700                 else:
701                     tb = sys.exc_info()
702                     self._reports[id]['exception'] = openerp.exceptions.DeferredException(tools.exception_to_unicode(exception), tb)
703                 self._reports[id]['state'] = True
704             cr.commit()
705             cr.close()
706             return True
707
708         thread.start_new_thread(go, (id, uid, ids, datas, context))
709         return id
710
711     def _check_report(self, report_id):
712         result = self._reports[report_id]
713         exc = result['exception']
714         if exc:
715             netsvc.abort_response(exc, exc.message, 'warning', exc.traceback)
716         res = {'state': result['state']}
717         if res['state']:
718             if tools.config['reportgz']:
719                 import zlib
720                 res2 = zlib.compress(result['result'])
721                 res['code'] = 'zlib'
722             else:
723                 #CHECKME: why is this needed???
724                 if isinstance(result['result'], unicode):
725                     res2 = result['result'].encode('latin1', 'replace')
726                 else:
727                     res2 = result['result']
728             if res2:
729                 res['result'] = base64.encodestring(res2)
730             res['format'] = result['format']
731             del self._reports[report_id]
732         return res
733
734     def exp_report_get(self, db, uid, report_id):
735         if report_id in self._reports:
736             if self._reports[report_id]['uid'] == uid:
737                 return self._check_report(report_id)
738             else:
739                 raise Exception, 'AccessDenied'
740         else:
741             raise Exception, 'ReportNotFound'
742
743
744 def start_service():
745     db()
746     common()
747     objects_proxy()
748     report_spool()
749
750
751 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: