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