Launchpad automatic translations update.
[odoo/odoo.git] / bin / 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 logging
24 import os
25 import security
26 import thread
27 import threading
28 import time
29 import sys
30 import platform
31 from tools.translate import _
32 import addons
33 import ir
34 import netsvc
35 import pooler
36 import release
37 import sql_db
38 import tools
39 import locale
40 from cStringIO import StringIO
41
42 logging.basicConfig()
43
44 class db(netsvc.ExportService):
45     def __init__(self, name="db"):
46         netsvc.ExportService.__init__(self, name)
47         self.joinGroup("web-services")
48         self.actions = {}
49         self.id = 0
50         self.id_protect = threading.Semaphore()
51
52         self._pg_psw_env_var_is_set = False # on win32, pg_dump need the PGPASSWORD env var
53
54     def dispatch(self, method, auth, params):
55         if method in [ 'create', 'get_progress', 'drop', 'dump', 
56             'restore', 'rename', 
57             'change_admin_password', 'migrate_databases' ]:
58             passwd = params[0]
59             params = params[1:]
60             security.check_super(passwd)
61         elif method in [ 'db_exist', 'list', 'list_lang', 'server_version' ]:
62             # params = params
63             # No security check for these methods
64             pass
65         else:
66             raise KeyError("Method not found: %s" % method)
67         fn = getattr(self, 'exp_'+method)
68         return fn(*params)
69     
70     def new_dispatch(self,method,auth,params):
71         pass
72     def _create_empty_database(self, name):
73         db = sql_db.db_connect('template1')
74         cr = db.cursor()
75         try:
76             cr.autocommit(True) # avoid transaction block
77             cr.execute("""CREATE DATABASE "%s" ENCODING 'unicode' TEMPLATE "template0" """ % name)
78         finally:
79             cr.close()
80
81     def exp_create(self, db_name, demo, lang, user_password='admin'):
82         self.id_protect.acquire()
83         self.id += 1
84         id = self.id
85         self.id_protect.release()
86
87         self.actions[id] = {'clean': False}
88
89         self._create_empty_database(db_name)
90
91         class DBInitialize(object):
92             def __call__(self, serv, id, db_name, demo, lang, user_password='admin'):
93                 cr = None
94                 try:
95                     serv.actions[id]['progress'] = 0
96                     cr = sql_db.db_connect(db_name).cursor()
97                     tools.init_db(cr)
98                     cr.commit()
99                     cr.close()
100                     cr = None
101                     pool = pooler.restart_pool(db_name, demo, serv.actions[id],
102                             update_module=True)[1]
103
104                     cr = sql_db.db_connect(db_name).cursor()
105
106                     if lang:
107                         modobj = pool.get('ir.module.module')
108                         mids = modobj.search(cr, 1, [('state', '=', 'installed')])
109                         modobj.update_translations(cr, 1, mids, lang)
110
111                     cr.execute('UPDATE res_users SET password=%s, context_lang=%s, active=True WHERE login=%s', (
112                         user_password, lang, 'admin'))
113                     cr.execute('SELECT login, password, name ' \
114                                '  FROM res_users ' \
115                                ' ORDER BY login')
116                     serv.actions[id]['users'] = cr.dictfetchall()
117                     serv.actions[id]['clean'] = True
118                     cr.commit()
119                     cr.close()
120                 except Exception, e:
121                     serv.actions[id]['clean'] = False
122                     serv.actions[id]['exception'] = e
123                     import traceback
124                     e_str = StringIO()
125                     traceback.print_exc(file=e_str)
126                     traceback_str = e_str.getvalue()
127                     e_str.close()
128                     netsvc.Logger().notifyChannel('web-services', netsvc.LOG_ERROR, 'CREATE DATABASE\n%s' % (traceback_str))
129                     serv.actions[id]['traceback'] = traceback_str
130                     if cr:
131                         cr.close()
132         logger = netsvc.Logger()
133         logger.notifyChannel("web-services", netsvc.LOG_INFO, 'CREATE DATABASE: %s' % (db_name.lower()))
134         dbi = DBInitialize()
135         create_thread = threading.Thread(target=dbi,
136                 args=(self, id, db_name, demo, lang, user_password))
137         create_thread.start()
138         self.actions[id]['thread'] = create_thread
139         return id
140
141     def exp_get_progress(self, id):
142         if self.actions[id]['thread'].isAlive():
143 #           return addons.init_progress[db_name]
144             return (min(self.actions[id].get('progress', 0),0.95), [])
145         else:
146             clean = self.actions[id]['clean']
147             if clean:
148                 users = self.actions[id]['users']
149                 self.actions.pop(id)
150                 return (1.0, users)
151             else:
152                 e = self.actions[id]['exception']
153                 self.actions.pop(id)
154                 raise Exception, e
155
156     def exp_drop(self, db_name):
157         sql_db.close_db(db_name)
158         logger = netsvc.Logger()
159
160         db = sql_db.db_connect('template1')
161         cr = db.cursor()
162         cr.autocommit(True) # avoid transaction block
163         try:
164             try:
165                 cr.execute('DROP DATABASE "%s"' % db_name)
166             except Exception, e:
167                 logger.notifyChannel("web-services", netsvc.LOG_ERROR,
168                         'DROP DB: %s failed:\n%s' % (db_name, e))
169                 raise Exception("Couldn't drop database %s: %s" % (db_name, e))
170             else:
171                 logger.notifyChannel("web-services", netsvc.LOG_INFO,
172                     'DROP DB: %s' % (db_name))
173         finally:
174             cr.close()
175         return True
176
177     def _set_pg_psw_env_var(self):
178         if os.name == 'nt' and not os.environ.get('PGPASSWORD', ''):
179             os.environ['PGPASSWORD'] = tools.config['db_password']
180             self._pg_psw_env_var_is_set = True
181
182     def _unset_pg_psw_env_var(self):
183         if os.name == 'nt' and self._pg_psw_env_var_is_set:
184             os.environ['PGPASSWORD'] = ''
185
186     def exp_dump(self, db_name):
187         logger = netsvc.Logger()
188
189         self._set_pg_psw_env_var()
190
191         cmd = ['pg_dump', '--format=c', '--no-owner']
192         if tools.config['db_user']:
193             cmd.append('--username=' + tools.config['db_user'])
194         if tools.config['db_host']:
195             cmd.append('--host=' + tools.config['db_host'])
196         if tools.config['db_port']:
197             cmd.append('--port=' + str(tools.config['db_port']))
198         cmd.append(db_name)
199
200         stdin, stdout = tools.exec_pg_command_pipe(*tuple(cmd))
201         stdin.close()
202         data = stdout.read()
203         res = stdout.close()
204         if res:
205             logger.notifyChannel("web-services", netsvc.LOG_ERROR,
206                     'DUMP DB: %s failed\n%s' % (db_name, data))
207             raise Exception, "Couldn't dump database"
208         logger.notifyChannel("web-services", netsvc.LOG_INFO,
209                 'DUMP DB: %s' % (db_name))
210
211         self._unset_pg_psw_env_var()
212
213         return base64.encodestring(data)
214
215     def exp_restore(self, db_name, data):
216         logger = netsvc.Logger()
217
218         self._set_pg_psw_env_var()
219
220         if self.exp_db_exist(db_name):
221             logger.notifyChannel("web-services", netsvc.LOG_WARNING,
222                     'RESTORE DB: %s already exists' % (db_name,))
223             raise Exception, "Database already exists"
224
225         self._create_empty_database(db_name)
226
227         cmd = ['pg_restore', '--no-owner']
228         if tools.config['db_user']:
229             cmd.append('--username=' + tools.config['db_user'])
230         if tools.config['db_host']:
231             cmd.append('--host=' + tools.config['db_host'])
232         if tools.config['db_port']:
233             cmd.append('--port=' + str(tools.config['db_port']))
234         cmd.append('--dbname=' + db_name)
235         args2 = tuple(cmd)
236
237         buf=base64.decodestring(data)
238         if os.name == "nt":
239             tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
240             file(tmpfile, 'wb').write(buf)
241             args2=list(args2)
242             args2.append(' ' + tmpfile)
243             args2=tuple(args2)
244         stdin, stdout = tools.exec_pg_command_pipe(*args2)
245         if not os.name == "nt":
246             stdin.write(base64.decodestring(data))
247         stdin.close()
248         res = stdout.close()
249         if res:
250             raise Exception, "Couldn't restore database"
251         logger.notifyChannel("web-services", netsvc.LOG_INFO,
252                 'RESTORE DB: %s' % (db_name))
253
254         self._unset_pg_psw_env_var()
255
256         return True
257
258     def exp_rename(self, old_name, new_name):
259         sql_db.close_db(old_name)
260         logger = netsvc.Logger()
261
262         db = sql_db.db_connect('template1')
263         cr = db.cursor()
264         try:
265             try:
266                 cr.execute('ALTER DATABASE "%s" RENAME TO "%s"' % (old_name, new_name))
267             except Exception, e:
268                 logger.notifyChannel("web-services", netsvc.LOG_ERROR,
269                         'RENAME DB: %s -> %s failed:\n%s' % (old_name, new_name, e))
270                 raise Exception("Couldn't rename database %s to %s: %s" % (old_name, new_name, e))
271             else:
272                 fs = os.path.join(tools.config['root_path'], 'filestore')
273                 if os.path.exists(os.path.join(fs, old_name)):
274                     os.rename(os.path.join(fs, old_name), os.path.join(fs, new_name))
275
276                 logger.notifyChannel("web-services", netsvc.LOG_INFO,
277                     'RENAME DB: %s -> %s' % (old_name, new_name))
278         finally:
279             cr.close()
280         return True
281
282     def exp_db_exist(self, db_name):
283         ## Not True: in fact, check if connection to database is possible. The database may exists
284         return bool(sql_db.db_connect(db_name))
285
286     def exp_list(self):
287         if not tools.config['list_db']:
288             raise Exception('AccessDenied')
289
290         db = sql_db.db_connect('template1')
291         cr = db.cursor()
292         try:
293             try:
294                 db_user = tools.config["db_user"]
295                 if not db_user and os.name == 'posix':
296                     import pwd
297                     db_user = pwd.getpwuid(os.getuid())[0]
298                 if not db_user:
299                     cr.execute("select decode(usename, 'escape') from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],))
300                     res = cr.fetchone()
301                     db_user = res and str(res[0])
302                 if db_user:
303                     cr.execute("select decode(datname, 'escape') from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in ('template0', 'template1', 'postgres') order by datname", (db_user,))
304                 else:
305                     cr.execute("select decode(datname, 'escape') from pg_database where datname not in('template0', 'template1','postgres') order by datname")
306                 res = [str(name) for (name,) in cr.fetchall()]
307             except:
308                 res = []
309         finally:
310             cr.close()
311         res.sort()
312         return res
313
314     def exp_change_admin_password(self, new_password):
315         tools.config['admin_passwd'] = new_password
316         tools.config.save()
317         return True
318
319     def exp_list_lang(self):
320         return tools.scan_languages()
321
322     def exp_server_version(self):
323         """ Return the version of the server
324             Used by the client to verify the compatibility with its own version
325         """
326         return release.version
327
328     def exp_migrate_databases(self,databases):
329
330         from osv.orm import except_orm
331         from osv.osv import except_osv
332
333         l = netsvc.Logger()
334         for db in databases:
335             try:
336                 l.notifyChannel('migration', netsvc.LOG_INFO, 'migrate database %s' % (db,))
337                 tools.config['update']['base'] = True
338                 pooler.restart_pool(db, force_demo=False, update_module=True)
339             except except_orm, inst:
340                 self.abortResponse(1, inst.name, 'warning', inst.value)
341             except except_osv, inst:
342                 self.abortResponse(1, inst.name, inst.exc_type, inst.value)
343             except Exception:
344                 import traceback
345                 tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
346                 l.notifyChannel('web-services', netsvc.LOG_ERROR, tb_s)
347                 raise
348         return True
349 db()
350
351 class _ObjectService(netsvc.ExportService):
352      "A common base class for those who have fn(db, uid, password,...) "
353
354      def common_dispatch(self, method, auth, params):
355         (db, uid, passwd ) = params[0:3]
356         params = params[3:]
357         security.check(db,uid,passwd)
358         cr = pooler.get_db(db).cursor()
359         fn = getattr(self, 'exp_'+method)
360         res = fn(cr, uid, *params)
361         cr.commit()
362         cr.close()
363         return res
364
365 class common(_ObjectService):
366     def __init__(self,name="common"):
367         _ObjectService.__init__(self,name)
368         self.joinGroup("web-services")
369
370     def dispatch(self, method, auth, params):
371         logger = netsvc.Logger()
372         if method in [ 'ir_set','ir_del', 'ir_get' ]:
373             return self.common_dispatch(method,auth,params)
374         if method == 'login':
375             # At this old dispatcher, we do NOT update the auth proxy
376             res = security.login(params[0], params[1], params[2])
377             msg = res and 'successful login' or 'bad login or password'
378             # TODO log the client ip address..
379             logger.notifyChannel("web-service", netsvc.LOG_INFO, "%s from '%s' using database '%s'" % (msg, params[1], params[0].lower()))
380             return res or False
381         elif method == 'logout':
382             if auth:
383                 auth.logout(params[1])
384             logger.notifyChannel("web-service", netsvc.LOG_INFO,'Logout %s from database %s'%(login,db))
385             return True
386         elif method in ['about', 'timezone_get', 'get_server_environment',
387                         'login_message','get_stats', 'check_connectivity']:
388             pass
389         elif method in ['get_available_updates', 'get_migration_scripts', 'set_loglevel']:
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
400     def new_dispatch(self,method,auth,params):
401         pass
402
403     def exp_ir_set(self, cr, uid, keys, args, name, value, replace=True, isobject=False):
404         res = ir.ir_set(cr,uid, keys, args, name, value, replace, isobject)
405         return res
406
407     def exp_ir_del(self, cr, uid, id):
408         res = ir.ir_del(cr,uid, id)
409         return res
410
411     def exp_ir_get(self, cr, uid, keys, args=None, meta=None, context=None):
412         if not args:
413             args=[]
414         if not context:
415             context={}
416         res = ir.ir_get(cr,uid, keys, args, meta, context)
417         return res
418
419     def exp_about(self, extended=False):
420         """Return information about the OpenERP Server.
421
422         @param extended: if True then return version info
423         @return string if extended is False else tuple
424         """
425
426         info = _('''
427
428 OpenERP is an ERP+CRM program for small and medium businesses.
429
430 The whole source code is distributed under the terms of the
431 GNU Public Licence.
432
433 (c) 2003-TODAY, Fabien Pinckaers - Tiny sprl''')
434
435         if extended:
436             return info, release.version
437         return info
438
439     def exp_timezone_get(self, db, login, password):
440         #timezone detection is safe in multithread, so lazy init is ok here
441         if (not tools.config['timezone']):
442             tools.config['timezone'] = tools.misc.detect_server_timezone()
443         return tools.config['timezone']
444
445
446     def exp_get_available_updates(self, contract_id, contract_password):
447         import tools.maintenance as tm
448         try:
449             rc = tm.remote_contract(contract_id, contract_password)
450             if not rc.id:
451                 raise tm.RemoteContractException('This contract does not exist or is not active')
452
453             return rc.get_available_updates(rc.id, addons.get_modules_with_version())
454
455         except tm.RemoteContractException, e:
456             self.abortResponse(1, 'Migration Error', 'warning', str(e))
457
458
459     def exp_get_migration_scripts(self, contract_id, contract_password):
460         l = netsvc.Logger()
461         import tools.maintenance as tm
462         try:
463             rc = tm.remote_contract(contract_id, contract_password)
464             if not rc.id:
465                 raise tm.RemoteContractException('This contract does not exist or is not active')
466             if rc.status != 'full':
467                 raise tm.RemoteContractException('Can not get updates for a partial contract')
468
469             l.notifyChannel('migration', netsvc.LOG_INFO, 'starting migration with contract %s' % (rc.name,))
470
471             zips = rc.retrieve_updates(rc.id, addons.get_modules_with_version())
472
473             from shutil import rmtree, copytree, copy
474
475             backup_directory = os.path.join(tools.config['root_path'], 'backup', time.strftime('%Y-%m-%d-%H-%M'))
476             if zips and not os.path.isdir(backup_directory):
477                 l.notifyChannel('migration', netsvc.LOG_INFO, 'create a new backup directory to \
478                                 store the old modules: %s' % (backup_directory,))
479                 os.makedirs(backup_directory)
480
481             for module in zips:
482                 l.notifyChannel('migration', netsvc.LOG_INFO, 'upgrade module %s' % (module,))
483                 mp = addons.get_module_path(module)
484                 if mp:
485                     if os.path.isdir(mp):
486                         copytree(mp, os.path.join(backup_directory, module))
487                         if os.path.islink(mp):
488                             os.unlink(mp)
489                         else:
490                             rmtree(mp)
491                     else:
492                         copy(mp + 'zip', backup_directory)
493                         os.unlink(mp + '.zip')
494
495                 try:
496                     try:
497                         base64_decoded = base64.decodestring(zips[module])
498                     except:
499                         l.notifyChannel('migration', netsvc.LOG_ERROR, 'unable to read the module %s' % (module,))
500                         raise
501
502                     zip_contents = StringIO(base64_decoded)
503                     zip_contents.seek(0)
504                     try:
505                         try:
506                             tools.extract_zip_file(zip_contents, tools.config['addons_path'] )
507                         except:
508                             l.notifyChannel('migration', netsvc.LOG_ERROR, 'unable to extract the module %s' % (module, ))
509                             rmtree(module)
510                             raise
511                     finally:
512                         zip_contents.close()
513                 except:
514                     l.notifyChannel('migration', netsvc.LOG_ERROR, 'restore the previous version of the module %s' % (module, ))
515                     nmp = os.path.join(backup_directory, module)
516                     if os.path.isdir(nmp):
517                         copytree(nmp, tools.config['addons_path'])
518                     else:
519                         copy(nmp+'.zip', tools.config['addons_path'])
520                     raise
521
522             return True
523         except tm.RemoteContractException, e:
524             self.abortResponse(1, 'Migration Error', 'warning', str(e))
525         except Exception, e:
526             import traceback
527             tb_s = reduce(lambda x, y: x+y, traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback))
528             l.notifyChannel('migration', netsvc.LOG_ERROR, tb_s)
529             raise
530
531     def exp_get_server_environment(self):
532         os_lang = '.'.join( [x for x in locale.getdefaultlocale() if x] )
533         if not os_lang:
534             os_lang = 'NOT SET'
535         environment = '\nEnvironment Information : \n' \
536                      'System : %s\n' \
537                      'OS Name : %s\n' \
538                      %(platform.platform(), platform.os.name)
539         if os.name == 'posix':
540           if platform.system() == 'Linux':
541              lsbinfo = os.popen('lsb_release -a').read()
542              environment += '%s'%(lsbinfo)
543           else:
544              environment += 'Your System is not lsb compliant\n'
545         environment += 'Operating System Release : %s\n' \
546                     'Operating System Version : %s\n' \
547                     'Operating System Architecture : %s\n' \
548                     'Operating System Locale : %s\n'\
549                     'Python Version : %s\n'\
550                     'OpenERP-Server Version : %s'\
551                     %(platform.release(), platform.version(), platform.architecture()[0],
552                       os_lang, platform.python_version(),release.version)
553         return environment
554
555     def exp_login_message(self):
556         return tools.config.get('login_message', False)
557
558     def exp_set_loglevel(self,loglevel):
559         l = netsvc.Logger()
560         l.set_loglevel(int(loglevel))
561         return True
562
563     def exp_get_stats(self):
564         import threading
565         res = "OpenERP server: %d threads\n" % threading.active_count()
566         res += netsvc.Server.allStats()
567         return res
568
569     def exp_check_connectivity(self):
570         return bool(sql_db.db_connect('template1'))
571
572 common()
573
574 class objects_proxy(netsvc.ExportService):
575     def __init__(self, name="object"):
576         netsvc.ExportService.__init__(self,name)
577         self.joinGroup('web-services')
578
579     def dispatch(self, method, auth, params):
580         (db, uid, passwd ) = params[0:3]
581         params = params[3:]
582         if method not in ['execute','exec_workflow','obj_list']:
583             raise KeyError("Method not supported %s" % method)
584         security.check(db,uid,passwd)
585         ls = netsvc.LocalService('object_proxy')
586         fn = getattr(ls, method)
587         res = fn(db, uid, *params)
588         return res
589
590     
591     def new_dispatch(self,method,auth,params):
592         pass
593
594 objects_proxy()
595
596
597 #
598 # Wizard ID: 1
599 #    - None = end of wizard
600 #
601 # Wizard Type: 'form'
602 #    - form
603 #    - print
604 #
605 # Wizard datas: {}
606 # TODO: change local request to OSE request/reply pattern
607 #
608 class wizard(netsvc.ExportService):
609     def __init__(self, name='wizard'):
610         netsvc.ExportService.__init__(self,name)
611         self.joinGroup('web-services')
612         self.id = 0
613         self.wiz_datas = {}
614         self.wiz_name = {}
615         self.wiz_uid = {}
616
617     def dispatch(self, method, auth, params):
618         (db, uid, passwd ) = params[0:3]
619         params = params[3:]
620         if method not in ['execute','create']:
621             raise KeyError("Method not supported %s" % method)
622         security.check(db,uid,passwd)
623         fn = getattr(self, 'exp_'+method)
624         res = fn(db, uid, *params)
625         return res
626     
627     def new_dispatch(self,method,auth,params):
628         pass
629
630     def _execute(self, db, uid, wiz_id, datas, action, context):
631         self.wiz_datas[wiz_id].update(datas)
632         wiz = netsvc.LocalService('wizard.'+self.wiz_name[wiz_id])
633         return wiz.execute(db, uid, self.wiz_datas[wiz_id], action, context)
634
635     def exp_create(self, db, uid, wiz_name, datas=None):
636         if not datas:
637             datas={}
638 #FIXME: this is not thread-safe
639         self.id += 1
640         self.wiz_datas[self.id] = {}
641         self.wiz_name[self.id] = wiz_name
642         self.wiz_uid[self.id] = uid
643         return self.id
644
645     def exp_execute(self, db, uid, wiz_id, datas, action='init', context=None):
646         if not context:
647             context={}
648
649         if wiz_id in self.wiz_uid:
650             if self.wiz_uid[wiz_id] == uid:
651                 return self._execute(db, uid, wiz_id, datas, action, context)
652             else:
653                 raise Exception, 'AccessDenied'
654         else:
655             raise Exception, 'WizardNotFound'
656 wizard()
657
658 #
659 # TODO: set a maximum report number per user to avoid DOS attacks
660 #
661 # Report state:
662 #     False -> True
663 #
664
665 class ExceptionWithTraceback(Exception):
666     def __init__(self, msg, tb):
667         self.message = msg
668         self.traceback = tb
669         self.args = (msg, tb)
670
671 class report_spool(netsvc.ExportService):
672     def __init__(self, name='report'):
673         netsvc.ExportService.__init__(self, name)
674         self.joinGroup('web-services')
675         self._reports = {}
676         self.id = 0
677         self.id_protect = threading.Semaphore()
678
679     def dispatch(self, method, auth, params):
680         (db, uid, passwd ) = params[0:3]
681         params = params[3:]
682         if method not in ['report','report_get']:
683             raise KeyError("Method not supported %s" % method)
684         security.check(db,uid,passwd)
685         fn = getattr(self, 'exp_' + method)
686         res = fn(db, uid, *params)
687         return res
688
689     
690     def new_dispatch(self,method,auth,params):
691         pass
692
693     def exp_report(self, db, uid, object, ids, datas=None, context=None):
694         if not datas:
695             datas={}
696         if not context:
697             context={}
698
699         self.id_protect.acquire()
700         self.id += 1
701         id = self.id
702         self.id_protect.release()
703
704         self._reports[id] = {'uid': uid, 'result': False, 'state': False, 'exception': None}
705
706         def go(id, uid, ids, datas, context):
707             cr = pooler.get_db(db).cursor()
708             import traceback
709             import sys
710             try:
711                 obj = netsvc.LocalService('report.'+object)
712                 (result, format) = obj.create(cr, uid, ids, datas, context)
713                 if not result:
714                     tb = sys.exc_info()
715                     self._reports[id]['exception'] = ExceptionWithTraceback('RML is not available at specified location or not enough data to print!', tb)
716                 self._reports[id]['result'] = result
717                 self._reports[id]['format'] = format
718                 self._reports[id]['state'] = True
719             except Exception, exception:
720                 
721                 tb = sys.exc_info()
722                 tb_s = "".join(traceback.format_exception(*tb))
723                 logger = netsvc.Logger()
724                 logger.notifyChannel('web-services', netsvc.LOG_ERROR,
725                         'Exception: %s\n%s' % (str(exception), tb_s))
726                 self._reports[id]['exception'] = ExceptionWithTraceback(tools.exception_to_unicode(exception), tb)
727                 self._reports[id]['state'] = True
728             cr.commit()
729             cr.close()
730             return True
731
732         thread.start_new_thread(go, (id, uid, ids, datas, context))
733         return id
734
735     def _check_report(self, report_id):
736         result = self._reports[report_id]
737         if result['exception']:
738             raise result['exception']
739         res = {'state': result['state']}
740         if res['state']:
741             if tools.config['reportgz']:
742                 import zlib
743                 res2 = zlib.compress(result['result'])
744                 res['code'] = 'zlib'
745             else:
746                 #CHECKME: why is this needed???
747                 if isinstance(result['result'], unicode):
748                     res2 = result['result'].encode('latin1', 'replace')
749                 else:
750                     res2 = result['result']
751             if res2:
752                 res['result'] = base64.encodestring(res2)
753             res['format'] = result['format']
754             del self._reports[report_id]
755         return res
756
757     def exp_report_get(self, db, uid, report_id):
758
759         if report_id in self._reports:
760             if self._reports[report_id]['uid'] == uid:
761                 return self._check_report(report_id)
762             else:
763                 raise Exception, 'AccessDenied'
764         else:
765             raise Exception, 'ReportNotFound'
766
767 report_spool()
768
769
770 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
771