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