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