diff options
| author | pinky <> | 2006-12-07 13:41:40 +0000 |
|---|---|---|
| committer | pinky <> | 2006-12-07 13:41:40 +0000 |
| commit | 004a0b996ff8f269451e07346f71a129a1f3fbaf (patch) | |
| tree | 48f43c1fc2ca4c1f4d5648e7e73553d3f4ccae06 /bin/service/web_services.py | |
New trunk
bzr revid: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
Diffstat (limited to 'bin/service/web_services.py')
| -rw-r--r-- | bin/service/web_services.py | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/bin/service/web_services.py b/bin/service/web_services.py new file mode 100644 index 00000000000..15836761ca0 --- /dev/null +++ b/bin/service/web_services.py @@ -0,0 +1,449 @@ +# -*- encoding: utf-8 -*- +############################################################################## +# +# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved. +# Fabien Pinckaers <fp@tiny.Be> +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract a Free Software +# Service Company +# +# This program is Free Software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +############################################################################## + +import base64, os, string + +import netsvc +import pooler, security, ir, tools +import logging + +import threading, thread + +import time +import base64 +import addons + +import sql_db + +logging.basicConfig() + +#TODO: use translation system +def _(str): + return str + +class db(netsvc.Service): + def __init__(self, name="db"): + netsvc.Service.__init__(self, name) + self.joinGroup("web-services") + self.exportMethod(self.create) + self.exportMethod(self.get_progress) + self.exportMethod(self.drop) + self.exportMethod(self.dump) + self.exportMethod(self.restore) + self.exportMethod(self.list) + self.exportMethod(self.list_lang) + self.exportMethod(self.change_admin_password) + self.actions = {} + self.id = 0 + self.id_protect = threading.Semaphore() + + def create(self, password, db_name, demo, lang): + security.check_super(password) + self.id_protect.acquire() + self.id += 1 + id = self.id + self.id_protect.release() + + self.actions[id] = {'clean': False} + + if tools.config['db_user']: + res = tools.exec_pg_command('createdb', '--encoding=unicode', '--username='+tools.config['db_user'], db_name) + else: + res = tools.exec_pg_command('createdb', '--encoding=unicode', db_name) + if not res: + class DBInitialize(object): + def __call__(self, serv, id, db_name, demo, lang): + try: + serv.actions[id]['progress'] = 0 + clean = False + cr = sql_db.db_connect(db_name).cursor() + tools.init_db(cr) + cr.commit() + cr.close() + pool = pooler.get_pool(db_name, demo,serv.actions[id], update_module=True) + #tools.init_db(cr) + if lang and lang != 'en_EN': + filename = tools.config["root_path"] + "/i18n/" + lang + ".csv" + tools.trans_load(db_name, filename, lang) + + serv.actions[id]['clean'] = True + + + cr = sql_db.db_connect(db_name).cursor() + cr.execute('select login,password,name from res_users where login<>\'root\' order by login') + serv.actions[id]['users'] = cr.dictfetchall() + cr.close() + except Exception, e: + serv.actions[id]['clean'] = False + serv.actions[id]['exception'] = e + from StringIO import StringIO + import traceback + e_str = StringIO() + traceback.print_exc(file=e_str) + traceback_str = e_str.getvalue() + e_str.close() + serv.actions[id]['traceback'] = traceback_str + + dbi = DBInitialize() + create_thread = threading.Thread(target=dbi, args=(self, id, db_name, demo, lang)) + create_thread.start() + self.actions[id]['thread'] = create_thread + return id + raise "Couldn't create database" + + def get_progress(self, password, id): + security.check_super(password) + if self.actions[id]['thread'].isAlive(): +# return addons.init_progress[db_name] + return (min(self.actions[id].get('progress', 0),0.95), []) + else: + clean = self.actions[id]['clean'] + if clean: + users = self.actions[id]['users'] + del self.actions[id] + return (1.0, users) + else: + print self.actions[id]['traceback'] + e = self.actions[id]['exception'] + del self.actions[id] + raise e + + def drop(self, password, db_name): + security.check_super(password) + pooler.close_db(db_name) + if tools.config['db_user']: + res = tools.exec_pg_command('dropdb', '--username='+tools.config['db_user'], db_name) + else: + res = tools.exec_pg_command('dropdb', db_name) + if res: + raise "Couldn't drop database" + else: + return True + + def dump(self, password, db_name): + security.check_super(password) + if tools.config['db_user']: + args = ('pg_dump', '--format=c', '-U', tools.config['db_user'], db_name) + else: + args = ('pg_dump', '--format=c', db_name) + stdin, stdout = tools.exec_pg_command_pipe(*args) + stdin.close() + res = stdout.read() + stdout.close() + return base64.encodestring(res) + + def restore(self, password, db_name, data): + security.check_super(password) + res = True + if self.db_exist(db_name): + raise "Database already exists" + else: + if tools.config['db_user']: + args = ('createdb', '--encoding=unicode', '--username='+tools.config['db_user'], db_name) + args2 = ('pg_restore', '-U', tools.config['db_user'], '-d %s' % db_name) + else: + args = ('createdb', '--encoding=unicode', db_name) + args2 = ('pg_restore', '-d %s' % db_name) + res = tools.exec_pg_command(*args) + if not res: + + buf=base64.decodestring(data) + if os.name == "nt": + tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam() + file(tmpfile, 'wb').write(buf) + args2=list(args2) + args2.append(' ' + tmpfile) + args2=tuple(args2) + stdin, stdout = tools.exec_pg_command_pipe(*args2) + if not os.name == "nt": + stdin.write(base64.decodestring(data)) + stdin.close() + stdout.close() + return True + raise "Couldn't create database" + + def db_exist(self, db_name): + try: + db = sql_db.db_connect(db_name) + db.truedb.close() + return True + except: + return False + +# cr.execute("select datname from pg_database where datname=%s", (db_name,)) +# res = bool(cr.rowcount) +# cr.close() +# del cr +# return res + + def list(self): + db = sql_db.db_connect('template1') + try: + cr = db.cursor() + db_user = tools.config["db_user"] + if not db_user and os.name == 'posix': + import pwd + db_user = pwd.getpwuid(os.getuid())[0] + if not db_user: + cr.execute("select usename from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],)) + res = cr.fetchone() + db_user = res and res[0] + if db_user: + cr.execute("select datname from pg_database where datdba=(select usesysid from pg_user where usename=%s) and datname not in ('template0', 'template1', 'postgres')", (db_user,)) + else: + cr.execute("select datname from pg_database where datname not in('template0', 'template1','postgres')") + res = [name for (name,) in cr.fetchall()] + cr.close() + except: + res = [] + db.truedb.close() + return res + + def change_admin_password(self, old_password, new_password): + security.check_super(old_password) + tools.config['admin_passwd'] = new_password + tools.config.save() + return True + + def list_lang(self): + return tools.scan_languages() + import glob + file_list = glob.glob(os.path.join(tools.config['root_path'], 'i18n', '*.csv')) + def lang_tuple(fname): + lang_dict=tools.get_languages() + lang = os.path.basename(fname).split(".")[0] + return (lang, lang_dict.get(lang, lang)) + return [lang_tuple(fname) for fname in file_list] +db() + +class common(netsvc.Service): + def __init__(self,name="common"): + netsvc.Service.__init__(self,name) + self.joinGroup("web-services") + self.exportMethod(self.ir_get) + self.exportMethod(self.ir_set) + self.exportMethod(self.ir_del) + self.exportMethod(self.about) + self.exportMethod(self.login) + + def ir_set(self, db, uid, password, keys, args, name, value, replace=True, isobject=False): + security.check(db, uid, password) + cr = pooler.get_db(db).cursor() + res = ir.ir_set(cr,uid, keys, args, name, value, replace, isobject) + cr.commit() + cr.close() + return res + + def ir_del(self, db, uid, password, id): + security.check(db, uid, password) + cr = pooler.get_db(db).cursor() + res = ir.ir_del(cr,uid, id) + cr.commit() + cr.close() + return res + + def ir_get(self, db, uid, password, keys, args=[], meta=None, context={}): + security.check(db, uid, password) + cr = pooler.get_db(db).cursor() + res = ir.ir_get(cr,uid, keys, args, meta, context) + cr.commit() + cr.close() + return res + + def login(self, db, login, password): + logger = netsvc.Logger() + cr = pooler.get_db(db).cursor() +#FIXME: this is a temporary fix for the crash on login/password with non ASCII chars. +#We should fix this in a better way (ie somewhere else) + cr.execute('select id from res_users where login=%s and password=%s', (login.encode('utf-8'), password.encode('utf-8'))) + res = cr.fetchone() + msg = res and 'successful login' or 'bad login or password' + logger.notifyChannel("web-service", netsvc.LOG_INFO, "%s from '%s' using database '%s'" % (msg, login, db)) + cr.close() + return (res and res[0]) or False + + def about(self): + return tools.version_string + _(''' + +Tiny ERP is an ERP+CRM program for small and medium businesses. + +The whole source code is distributed under the terms of the +GNU Public Licence. + +(c) 2003-TODAY, Fabien Pinckaers - Tiny sprl''') +common() + +class objects_proxy(netsvc.Service): + def __init__(self, name="object"): + netsvc.Service.__init__(self,name) + self.joinGroup('web-services') + self.exportMethod(self.execute) + self.exportMethod(self.exec_workflow) + self.exportMethod(self.obj_list) + + def exec_workflow(self, db, uid, passwd, object, method, id): + security.check(db, uid, passwd) + service = netsvc.LocalService("object_proxy") + res = service.exec_workflow(db, uid, object, method, id) + return res + + def execute(self, db, uid, passwd, object, method, *args): + security.check(db, uid, passwd) + service = netsvc.LocalService("object_proxy") + res = service.execute(db, uid, object, method, *args) + return res + + def obj_list(self, db, uid, passwd): + security.check(db, uid, passwd) + service = netsvc.LocalService("object_proxy") + res = service.obj_list() + return res +objects_proxy() + + +# +# Wizard ID: 1 +# - None = end of wizard +# +# Wizard Type: 'form' +# - form +# - print +# +# Wizard datas: {} +# TODO: change local request to OSE request/reply pattern +# +class wizard(netsvc.Service): + def __init__(self, name='wizard'): + netsvc.Service.__init__(self,name) + self.joinGroup('web-services') + self.exportMethod(self.execute) + self.exportMethod(self.create) + self.id = 0 + self.wiz_datas = {} + self.wiz_name = {} + self.wiz_uid = {} + + def _execute(self, db, uid, wiz_id, datas, action, context): + self.wiz_datas[wiz_id].update(datas) + wiz = netsvc.LocalService('wizard.'+self.wiz_name[wiz_id]) + return wiz.execute(db, uid, self.wiz_datas[wiz_id], action, context) + + def create(self, db, uid, passwd, wiz_name, datas={}): + security.check(db, uid, passwd) +#FIXME: this is not thread-safe + self.id += 1 + self.wiz_datas[self.id] = {} + self.wiz_name[self.id] = wiz_name + self.wiz_uid[self.id] = uid + return self.id + + def execute(self, db, uid, passwd, wiz_id, datas, action='init', context={}): + security.check(db, uid, passwd) + + if wiz_id in self.wiz_uid: + if self.wiz_uid[wiz_id] == uid: + return self._execute(db, uid, wiz_id, datas, action, context) + else: + raise 'AccessDenied' + else: + raise 'WizardNotFound' +wizard() + +# +# TODO: set a maximum report number per user to avoid DOS attacks +# +# Report state: +# False -> True +# +class report_spool(netsvc.Service): + def __init__(self, name='report'): + netsvc.Service.__init__(self, name) + self.joinGroup('web-services') + self.exportMethod(self.report) + self.exportMethod(self.report_get) + self._reports = {} + self.id = 0 + self.id_protect = threading.Semaphore() + + def report(self, db, uid, passwd, object, ids, datas={}, context={}): + security.check(db, uid, passwd) + + self.id_protect.acquire() + self.id += 1 + id = self.id + self.id_protect.release() + + self._reports[id] = {'uid': uid, 'result': False, 'state': False} + + def go(id, uid, ids, datas, context): + cr = pooler.get_db(db).cursor() + obj = netsvc.LocalService('report.'+object) + (result, format) = obj.create(cr, uid, ids, datas, context) + cr.close() + self._reports[id]['result'] = result + self._reports[id]['format'] = format + self._reports[id]['state'] = True + return True + + thread.start_new_thread(go, (id, uid, ids, datas, context)) + return id + + def _check_report(self, report_id): + result = self._reports[report_id] + res = {'state': result['state']} + if res['state']: + if tools.config['reportgz']: + import zlib + res2 = zlib.compress(result['result']) + res['code'] = 'zlib' + else: + #CHECKME: why is this needed??? + if isinstance(result['result'], unicode): + res2 = result['result'].encode('latin1', 'replace') + else: + res2 = result['result'] + res['result'] = base64.encodestring(res2) + res['format'] = result['format'] + del self._reports[report_id] + return res + + def report_get(self, db, uid, passwd, report_id): + security.check(db, uid, passwd) + + if report_id in self._reports: + if self._reports[report_id]['uid'] == uid: + return self._check_report(report_id) + else: + raise 'AccessDenied' + else: + raise 'ReportNotFound' + +report_spool() + +# vim:noexpandtab |
