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