940ad3ab61415440cf2ccc1df887d79d5e57066c
[odoo/odoo.git] / bin / service / web_services.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 # Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
5 #                    Fabien Pinckaers <fp@tiny.Be>
6 #
7 # WARNING: This program as such is intended to be used by professional
8 # programmers who take the whole responsability of assessing all potential
9 # consequences resulting from its eventual inadequacies and bugs
10 # End users who are looking for a ready-to-use solution with commercial
11 # garantees and support are strongly adviced to contract a Free Software
12 # Service Company
13 #
14 # This program is Free Software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
27 #
28 ##############################################################################
29
30 import base64, os, string
31
32 import netsvc
33 import pooler, security, ir, tools
34 import logging
35
36 import threading, thread
37
38 import time
39 import base64
40 import addons
41
42 import sql_db
43
44 logging.basicConfig()
45
46 #TODO: use translation system
47 def _(str):
48         return str
49
50 class db(netsvc.Service):
51         def __init__(self, name="db"):
52                 netsvc.Service.__init__(self, name)
53                 self.joinGroup("web-services")
54                 self.exportMethod(self.create)
55                 self.exportMethod(self.get_progress)
56                 self.exportMethod(self.drop)
57                 self.exportMethod(self.dump)
58                 self.exportMethod(self.restore)
59                 self.exportMethod(self.list)
60                 self.exportMethod(self.list_lang)
61                 self.exportMethod(self.change_admin_password)
62                 self.actions = {}
63                 self.id = 0
64                 self.id_protect = threading.Semaphore()
65
66         def create(self, password, db_name, demo, lang):
67                 security.check_super(password)
68                 self.id_protect.acquire()
69                 self.id += 1
70                 id = self.id
71                 self.id_protect.release()
72
73                 self.actions[id] = {'clean': False}
74
75                 if tools.config['db_user']:
76                         res = tools.exec_pg_command('createdb', '--quiet', '--encoding=unicode', '--username='+tools.config['db_user'], db_name)
77                 else:
78                         res = tools.exec_pg_command('createdb', '--quiet', '--encoding=unicode', db_name)
79                 if not res:
80                         class DBInitialize(object):
81                                 def __call__(self, serv, id, db_name, demo, lang):
82                                         try:
83                                                 serv.actions[id]['progress'] = 0
84                                                 clean = False
85                                                 cr = sql_db.db_connect(db_name).cursor()
86                                                 tools.init_db(cr)
87                                                 cr.commit()
88                                                 cr.close()
89                                                 pool = pooler.get_pool(db_name, demo,serv.actions[id], update_module=True)
90                                                 #tools.init_db(cr)
91                                                 if lang and lang != 'en_EN':
92                                                         filename = tools.config["root_path"] + "/i18n/" + lang + ".csv"
93                                                         tools.trans_load(db_name, filename, lang) 
94
95                                                 serv.actions[id]['clean'] = True 
96
97
98                                                 cr = sql_db.db_connect(db_name).cursor()
99                                                 cr.execute('select login,password,name from res_users where login<>\'root\' order by login')
100                                                 serv.actions[id]['users'] = cr.dictfetchall()
101                                                 cr.close()
102                                         except Exception, e:
103                                                 serv.actions[id]['clean'] = False
104                                                 serv.actions[id]['exception'] = e
105                                                 from StringIO import StringIO
106                                                 import traceback
107                                                 e_str = StringIO()
108                                                 traceback.print_exc(file=e_str)
109                                                 traceback_str = e_str.getvalue()
110                                                 e_str.close()
111                                                 serv.actions[id]['traceback'] = traceback_str
112                         logger = netsvc.Logger()
113                         logger.notifyChannel("web-services", netsvc.LOG_INFO, 'CREATE DB: %s' % (db_name))
114
115                         dbi = DBInitialize()
116                         create_thread = threading.Thread(target=dbi, args=(self, id, db_name, demo, lang))
117                         create_thread.start()
118                         self.actions[id]['thread'] = create_thread
119                         return id
120                 raise "Couldn't create database"
121         
122         def get_progress(self, password, id):
123                 security.check_super(password)
124                 if self.actions[id]['thread'].isAlive():
125 #                       return addons.init_progress[db_name]
126                         return (min(self.actions[id].get('progress', 0),0.95), [])
127                 else:
128                         clean = self.actions[id]['clean']
129                         if clean:
130                                 users = self.actions[id]['users']
131                                 del self.actions[id]
132                                 return (1.0, users)
133                         else:
134                                 print self.actions[id]['traceback']
135                                 e = self.actions[id]['exception']
136                                 del self.actions[id]
137                                 raise e
138
139         def drop(self, password, db_name):
140                 security.check_super(password)
141                 pooler.close_db(db_name)
142                 if tools.config['db_user']:
143                         res = tools.exec_pg_command('dropdb', '--quiet', '--username='+tools.config['db_user'], db_name)
144                 else:
145                         res = tools.exec_pg_command('dropdb', '--quiet', db_name)
146                 if res:
147                         raise "Couldn't drop database"
148                 else:
149                         logger = netsvc.Logger()
150                         logger.notifyChannel("web-services", netsvc.LOG_INFO, 'DROP DB: %s' % (db_name))
151                         return True
152
153         def dump(self, password, db_name):
154                 security.check_super(password)
155                 if tools.config['db_user']:
156                         args = ('pg_dump', '--format=c', '-U', tools.config['db_user'], db_name)
157                 else:
158                         args = ('pg_dump', '--format=c', db_name)
159                 stdin, stdout = tools.exec_pg_command_pipe(*args)
160                 stdin.close()
161                 data = stdout.read()
162                 res = stdout.close()
163                 if res:
164                         raise "Couldn't dump database"
165                 logger = netsvc.Logger()
166                 logger.notifyChannel("web-services", netsvc.LOG_INFO, 'DUMP DB: %s' % (db_name))
167                 return base64.encodestring(data)
168
169         def restore(self, password, db_name, data):
170                 security.check_super(password)
171                 res = True
172                 if self.db_exist(db_name):
173                         raise "Database already exists"
174                 else:
175                         if tools.config['db_user']:
176                                 args = ('createdb', '--quiet', '--encoding=unicode', '--username='+tools.config['db_user'], db_name)
177                                 args2 = ('pg_restore', '-U', tools.config['db_user'], '-d %s' % db_name)
178                         else:
179                                 args = ('createdb', '--quiet', '--encoding=unicode', db_name)
180                                 args2 = ('pg_restore', '-d %s' % db_name)
181                         res = tools.exec_pg_command(*args)
182                 if not res:
183
184                         buf=base64.decodestring(data)
185                         if os.name == "nt":
186                                 tmpfile = (os.environ['TMP'] or 'C:\\') + os.tmpnam()
187                                 file(tmpfile, 'wb').write(buf)
188                                 args2=list(args2)
189                                 args2.append(' ' + tmpfile)
190                                 args2=tuple(args2)
191                         stdin, stdout = tools.exec_pg_command_pipe(*args2)
192                         if not os.name == "nt":
193                                 stdin.write(base64.decodestring(data))
194                         stdin.close()
195                         res = stdout.close()
196                         if res:
197                                 raise "Couldn't restore database"
198                         logger = netsvc.Logger()
199                         logger.notifyChannel("web-services", netsvc.LOG_INFO, 'RESTORE DB: %s' % (db_name))
200                         return True
201                 raise "Couldn't create database"
202                 
203         def db_exist(self, db_name):
204                 try:
205                         db = sql_db.db_connect(db_name)
206                         db.truedb.close()
207                         return True
208                 except:
209                         return False
210                         
211 #               cr.execute("select datname from pg_database where datname=%s", (db_name,))
212 #               res = bool(cr.rowcount)
213 #               cr.close()
214 #               del cr
215 #               return res
216                 
217         def list(self):
218                 db = sql_db.db_connect('template1')
219                 try:
220                         cr = db.cursor()
221                         db_user = tools.config["db_user"]
222                         if not db_user and os.name == 'posix':
223                                 import pwd
224                                 db_user = pwd.getpwuid(os.getuid())[0] 
225                         if not db_user:
226                                 cr.execute("select usename from pg_user where usesysid=(select datdba from pg_database where datname=%s)", (tools.config["db_name"],))
227                                 res = cr.fetchone()
228                                 db_user = res and res[0]
229                         if db_user:
230                                 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,))
231                         else:
232                                 cr.execute("select datname from pg_database where datname not in('template0', 'template1','postgres')")
233                         res = [name for (name,) in cr.fetchall()]
234                         cr.close()
235                 except:
236                         res = []
237                 db.truedb.close()
238                 return res
239
240         def change_admin_password(self, old_password, new_password):
241                 security.check_super(old_password)
242                 tools.config['admin_passwd'] = new_password 
243                 tools.config.save()
244                 return True
245         
246         def list_lang(self):
247                 return tools.scan_languages()
248                 import glob
249                 file_list = glob.glob(os.path.join(tools.config['root_path'], 'i18n', '*.csv'))
250                 def lang_tuple(fname):
251                         lang_dict=tools.get_languages()
252                         lang = os.path.basename(fname).split(".")[0]
253                         return (lang, lang_dict.get(lang, lang))
254                 return [lang_tuple(fname) for fname in file_list]
255 db()
256
257 class common(netsvc.Service):
258         def __init__(self,name="common"):
259                 netsvc.Service.__init__(self,name)
260                 self.joinGroup("web-services")
261                 self.exportMethod(self.ir_get)
262                 self.exportMethod(self.ir_set)
263                 self.exportMethod(self.ir_del)
264                 self.exportMethod(self.about)
265                 self.exportMethod(self.login)
266
267         def ir_set(self, db, uid, password, keys, args, name, value, replace=True, isobject=False):
268                 security.check(db, uid, password)
269                 cr = pooler.get_db(db).cursor()
270                 res = ir.ir_set(cr,uid, keys, args, name, value, replace, isobject)
271                 cr.commit()
272                 cr.close()
273                 return res
274
275         def ir_del(self, db, uid, password, id):
276                 security.check(db, uid, password)
277                 cr = pooler.get_db(db).cursor()
278                 res = ir.ir_del(cr,uid, id)
279                 cr.commit()
280                 cr.close()
281                 return res
282
283         def ir_get(self, db, uid, password, keys, args=[], meta=None, context={}):
284                 security.check(db, uid, password)
285                 cr = pooler.get_db(db).cursor()
286                 res = ir.ir_get(cr,uid, keys, args, meta, context)
287                 cr.commit()
288                 cr.close()
289                 return res
290
291         def login(self, db, login, password):
292                 res = security.login(db, login, password)
293                 logger = netsvc.Logger()
294                 msg = res and 'successful login' or 'bad login or password'
295                 logger.notifyChannel("web-service", netsvc.LOG_INFO, "%s from '%s' using database '%s'" % (msg, login, db))
296                 return res or False
297
298         def about(self):
299                 return tools.version_string + _('''
300
301 Tiny ERP is an ERP+CRM program for small and medium businesses.
302
303 The whole source code is distributed under the terms of the
304 GNU Public Licence.
305
306 (c) 2003-TODAY, Fabien Pinckaers - Tiny sprl''')
307 common()
308
309 class objects_proxy(netsvc.Service):
310         def __init__(self, name="object"):
311                 netsvc.Service.__init__(self,name)
312                 self.joinGroup('web-services')
313                 self.exportMethod(self.execute)
314                 self.exportMethod(self.exec_workflow)
315                 self.exportMethod(self.obj_list)
316                 
317         def exec_workflow(self, db, uid, passwd, object, method, id):
318                 security.check(db, uid, passwd)
319                 service = netsvc.LocalService("object_proxy")
320                 res = service.exec_workflow(db, uid, object, method, id)
321                 return res
322                 
323         def execute(self, db, uid, passwd, object, method, *args):
324                 security.check(db, uid, passwd)
325                 service = netsvc.LocalService("object_proxy")
326                 res = service.execute(db, uid, object, method, *args)
327                 return res
328
329         def obj_list(self, db, uid, passwd):
330                 security.check(db, uid, passwd)
331                 service = netsvc.LocalService("object_proxy")
332                 res = service.obj_list()
333                 return res
334 objects_proxy()
335
336
337 #
338 # Wizard ID: 1
339 #    - None = end of wizard
340 #
341 # Wizard Type: 'form'
342 #    - form
343 #    - print
344 #
345 # Wizard datas: {}
346 # TODO: change local request to OSE request/reply pattern
347 #
348 class wizard(netsvc.Service):
349         def __init__(self, name='wizard'):
350                 netsvc.Service.__init__(self,name)
351                 self.joinGroup('web-services')
352                 self.exportMethod(self.execute)
353                 self.exportMethod(self.create)
354                 self.id = 0
355                 self.wiz_datas = {}
356                 self.wiz_name = {}
357                 self.wiz_uid = {}
358
359         def _execute(self, db, uid, wiz_id, datas, action, context):
360                 self.wiz_datas[wiz_id].update(datas)
361                 wiz = netsvc.LocalService('wizard.'+self.wiz_name[wiz_id])
362                 return wiz.execute(db, uid, self.wiz_datas[wiz_id], action, context)
363
364         def create(self, db, uid, passwd, wiz_name, datas={}):
365                 security.check(db, uid, passwd)
366 #FIXME: this is not thread-safe
367                 self.id += 1
368                 self.wiz_datas[self.id] = {}
369                 self.wiz_name[self.id] = wiz_name
370                 self.wiz_uid[self.id] = uid
371                 return self.id
372
373         def execute(self, db, uid, passwd, wiz_id, datas, action='init', context={}):
374                 security.check(db, uid, passwd)
375
376                 if wiz_id in self.wiz_uid:
377                         if self.wiz_uid[wiz_id] == uid:
378                                 return self._execute(db, uid, wiz_id, datas, action, context)
379                         else:
380                                 raise 'AccessDenied'
381                 else:
382                         raise 'WizardNotFound'
383 wizard()
384
385 #
386 # TODO: set a maximum report number per user to avoid DOS attacks
387 #
388 # Report state:
389 #     False -> True
390 #
391 class report_spool(netsvc.Service):
392         def __init__(self, name='report'):
393                 netsvc.Service.__init__(self, name)
394                 self.joinGroup('web-services')
395                 self.exportMethod(self.report)
396                 self.exportMethod(self.report_get)
397                 self._reports = {}
398                 self.id = 0
399                 self.id_protect = threading.Semaphore()
400
401         def report(self, db, uid, passwd, object, ids, datas={}, context={}):
402                 security.check(db, uid, passwd)
403                 
404                 self.id_protect.acquire()
405                 self.id += 1
406                 id = self.id
407                 self.id_protect.release()
408
409                 self._reports[id] = {'uid': uid, 'result': False, 'state': False}
410
411                 def go(id, uid, ids, datas, context):
412                         cr = pooler.get_db(db).cursor()
413                         obj = netsvc.LocalService('report.'+object)
414                         (result, format) = obj.create(cr, uid, ids, datas, context)
415                         cr.close()
416                         self._reports[id]['result'] = result
417                         self._reports[id]['format'] = format
418                         self._reports[id]['state'] = True
419                         return True
420
421                 thread.start_new_thread(go, (id, uid, ids, datas, context))
422                 return id
423
424         def _check_report(self, report_id):
425                 result = self._reports[report_id]
426                 res = {'state': result['state']}
427                 if res['state']:
428                         if tools.config['reportgz']:
429                                 import zlib
430                                 res2 = zlib.compress(result['result'])
431                                 res['code'] = 'zlib'
432                         else:
433                                 #CHECKME: why is this needed???
434                                 if isinstance(result['result'], unicode):
435                                         res2 = result['result'].encode('latin1', 'replace')
436                                 else:
437                                         res2 = result['result']
438                         if res2:
439                                 res['result'] = base64.encodestring(res2)
440                         res['format'] = result['format']
441                         del self._reports[report_id]
442                 return res
443
444         def report_get(self, db, uid, passwd, report_id):
445                 security.check(db, uid, passwd)
446
447                 if report_id in self._reports:
448                         if self._reports[report_id]['uid'] == uid:
449                                 return self._check_report(report_id)
450                         else:
451                                 raise 'AccessDenied'
452                 else:
453                         raise 'ReportNotFound'
454
455 report_spool()
456
457 # vim:noexpandtab