Use explicit callback instead of implicit gtk main iteration in rpc calls

The implicit gtk main iteration leads to bad ordered processing of events. So as
explicit is better than implicit, rpc calls that don't need to block until the
result use a callback.

review3461002
This commit is contained in:
Cédric Krier committed 2014-03-04 11:44:06 +01:00
1 parent ba78ccbaf4
commit 4ca7d9b11a
10 files changed
+276 -248

No files matched your search

+21 -13
View File
@@ -54,23 +54,31 @@ class Action(object):
@staticmethod
def execute(act_id, data, action_type=None, context=None):
if not action_type:
res = False
def get_action_type(actions):
try:
res, = RPCExecute('model', 'ir.action', 'read', [act_id],
['type'], context=context)
action, = actions()
except RPCException:
return
if not res:
raise Exception('ActionNotFound')
action_type = res['type']
try:
res, = RPCExecute('model', action_type, 'search_read',
action_type = action['type']
exec_action(action_type)
def exec_action(action_type):
def callback(actions):
try:
action, = actions()
except RPCException:
return
Action._exec_action(action, data)
RPCExecute('model', action_type, 'search_read',
[('action', '=', act_id)], 0, 1, None, None,
context=context)
except RPCException:
return
Action._exec_action(res, data)
context=context, callback=callback)
if not action_type:
RPCExecute('model', 'ir.action', 'read', [act_id],
['type'], context=context, callback=get_action_type)
else:
exec_action(action_type)
@staticmethod
def _exec_action(action, data=None, context=None):
+52 -27
View File
@@ -14,7 +14,6 @@ import unicodedata
from functools import partial
from tryton.config import CONFIG
from tryton.config import TRYTON_ICON, PIXMAPS_DIR
import time
import sys
import xmlrpclib
try:
@@ -1276,42 +1275,59 @@ class RPCProgress(object):
self.error = True
self.res = False
self.exception = exception
return True
if not self.res:
self.error = True
else:
if not self.res:
self.error = True
if self.callback:
# Post to GTK queue to be run by the main thread
gobject.idle_add(self.process)
return True
def run(self, process_exception_p=True, main_iteration_p=True):
session = rpc._SESSION
thread.start_new_thread(self.start, ())
def run(self, process_exception_p=True, callback=None):
self.process_exception_p = process_exception_p
self.callback = callback
self.session = rpc._SESSION
if self.parent.window:
watch = gtk.gdk.Cursor(gtk.gdk.WATCH)
self.parent.window.set_cursor(watch)
watch = None
i = 0
while (not self.res) and (not self.error):
i += 1
if i > 1:
watch = gtk.gdk.Cursor(gtk.gdk.WATCH)
self.parent.window.set_cursor(watch)
if main_iteration_p:
with gtk.gdk.lock:
while gtk.events_pending():
gtk.main_iteration()
time.sleep(0.1)
if callback:
thread.start_new_thread(self.start, ())
return
else:
self.start()
return self.process()
def process(self):
if self.parent.window:
self.parent.window.set_cursor(None)
if self.exception:
if process_exception_p:
if self.process_exception_p:
def rpc_execute(*args):
return RPCProgress('execute',
args).run(process_exception_p, main_iteration_p)
args).run(self.process_exception_p, self.callback)
result = process_exception(self.exception, *self.args,
rpc_execute=rpc_execute, session=session)
rpc_execute=rpc_execute, session=self.session)
if self.callback:
return
if result is False:
raise RPCException(self.exception)
return result
self.exception = RPCException(self.exception)
else:
self.exception = None
self.res = result
else:
self.exception
def return_():
if self.exception:
raise self.exception
return self.res
else:
return self.res
if self.callback:
self.callback(return_)
else:
return return_()
def RPCExecute(*args, **kwargs):
@@ -1320,8 +1336,17 @@ def RPCExecute(*args, **kwargs):
rpc_context.update(kwargs['context'])
args = args + (rpc_context,)
process_exception = kwargs.get('process_exception', True)
main_iteration = kwargs.get('main_iteration', True)
return RPCProgress('execute', args).run(process_exception, main_iteration)
callback = kwargs.get('callback')
return RPCProgress('execute', args).run(process_exception, callback)
def RPCContextReload(callback=None):
def update(context):
rpc.CONTEXT.clear()
rpc.CONTEXT.update(context())
if callback:
callback()
RPCExecute('model', 'res.user', 'get_preferences', True, callback=update)
class Tooltips(object):
+17 -14
View File
@@ -39,20 +39,23 @@ def update_completion(entry, record, field, model, domain=None):
domain = field.domain_get(record)
context = field.context_get(record)
domain = [('rec_name', 'ilike', '%' + search_text + '%'), domain]
try:
results = RPCExecute('model', model, 'search_read', domain, 0,
CONFIG['client.limit'], None, ['rec_name'], context=context,
process_exception=False)
except (TrytonError, TrytonServerError):
results = []
if search_text != entry.get_text().decode('utf-8'):
return False
completion_model.clear()
for result in results:
completion_model.append([result['rec_name'], result['id']])
completion_model.search_text = search_text
# Force display of popup
entry.emit('changed')
def callback(results):
try:
results = results()
except (TrytonError, TrytonServerError):
results = []
if search_text != entry.get_text().decode('utf-8'):
return False
completion_model.clear()
for result in results:
completion_model.append([result['rec_name'], result['id']])
completion_model.search_text = search_text
# Force display of popup
entry.emit('changed')
RPCExecute('model', model, 'search_read', domain, 0,
CONFIG['client.limit'], None, ['rec_name'], context=context,
process_exception=False, callback=callback)
return False
search_text = entry.get_text().decode('utf-8')
gobject.timeout_add(300, update, search_text, domain)
+33 -31
View File
@@ -23,10 +23,6 @@ def populate(menu, model, record, title=''):
return
elif record.id < 0:
return
try:
toolbar = RPCExecute('model', model, 'view_toolbar_get')
except RPCException:
return
def load(record):
if isinstance(record, (int, long)):
@@ -72,31 +68,37 @@ def populate(menu, model, record, title=''):
action_menu.append(attachment_item)
attachment_item.connect('activate', attachment)
for atype, icon, label, flavor in (
('action', 'tryton-executable', _('Actions...'), None),
('relate', 'tryton-go-jump', _('Relate...'), None),
('print', 'tryton-print-open', _('Report...'), 'open'),
('print', 'tryton-print-email', _('E-Mail...'), 'email'),
('print', 'tryton-print', _('Print...'), 'print'),
):
if len(action_menu):
action_menu.append(gtk.SeparatorMenuItem())
title_item = gtk.ImageMenuItem(icon)
title_item.set_label(label)
action_menu.append(title_item)
if not toolbar[atype]:
title_item.set_sensitive(False)
continue
submenu = gtk.Menu()
title_item.set_submenu(submenu)
for action in toolbar[atype]:
action = action.copy()
item = gtk.MenuItem(action['name'])
submenu.append(item)
if flavor == 'print':
action['direct_print'] = True
elif flavor == 'email':
action['email_print'] = True
item.connect('activate', activate, action, atype)
def set_toolbar(toolbar):
try:
toolbar = toolbar()
except RPCException:
return
for atype, icon, label, flavor in (
('action', 'tryton-executable', _('Actions...'), None),
('relate', 'tryton-go-jump', _('Relate...'), None),
('print', 'tryton-print-open', _('Report...'), 'open'),
('print', 'tryton-print-email', _('E-Mail...'), 'email'),
('print', 'tryton-print', _('Print...'), 'print'),
):
if len(action_menu):
action_menu.append(gtk.SeparatorMenuItem())
title_item = gtk.ImageMenuItem(icon)
title_item.set_label(label)
action_menu.append(title_item)
if not toolbar[atype]:
title_item.set_sensitive(False)
continue
submenu = gtk.Menu()
title_item.set_submenu(submenu)
for action in toolbar[atype]:
action = action.copy()
item = gtk.MenuItem(action['name'])
submenu.append(item)
if flavor == 'print':
action['direct_print'] = True
elif flavor == 'email':
action['email_print'] = True
item.connect('activate', activate, action, atype)
menu.show_all()
RPCExecute('model', model, 'view_toolbar_get', callback=set_toolbar)
menu.show_all()
+66 -57
View File
@@ -16,7 +16,7 @@ import webbrowser
import threading
import tryton.rpc as rpc
from tryton.common import RPCExecute, RPCException
from tryton.common import RPCExecute, RPCException, RPCContextReload
from tryton.config import CONFIG, TRYTON_ICON, get_config_dir
import tryton.common as common
from tryton.pyson import PYSONDecoder
@@ -325,31 +325,35 @@ class Main(object):
return False
if getattr(gmodel, 'search_text', None) == search_text:
return False
try:
result = RPCExecute('model', 'ir.model', 'global_search',
def callback(result):
try:
result = result()
except RPCException:
result = []
if search_text != widget.get_text().decode('utf-8'):
return False
gmodel.clear()
for r in result:
_, model, model_name, record_id, record_name, icon = r
if icon:
text = common.to_xml(record_name)
common.ICONFACTORY.register_icon(icon)
pixbuf = widget.render_icon(stock_id=icon,
size=gtk.ICON_SIZE_BUTTON, detail=None)
else:
text = '<b>%s:</b>\n %s' % (
common.to_xml(model_name),
common.to_xml(record_name))
pixbuf = None
gmodel.append([pixbuf, text, model, record_id])
gmodel.search_text = search_text
# Force display of popup
widget.emit('changed')
RPCExecute('model', 'ir.model', 'global_search',
search_text, CONFIG['client.limit'],
self.menu_screen.model_name)
except RPCException:
result = []
if search_text != widget.get_text().decode('utf-8'):
return False
gmodel.clear()
for r in result:
_, model, model_name, record_id, record_name, icon = r
if icon:
text = common.to_xml(record_name)
common.ICONFACTORY.register_icon(icon)
pixbuf = widget.render_icon(stock_id=icon,
size=gtk.ICON_SIZE_BUTTON, detail=None)
else:
text = '<b>%s:</b>\n %s' % (
common.to_xml(model_name),
common.to_xml(record_name))
pixbuf = None
gmodel.append([pixbuf, text, model, record_id])
gmodel.search_text = search_text
# Force display of popup
widget.emit('changed')
self.menu_screen.model_name, callback=callback)
return False
def changed(widget):
@@ -842,38 +846,43 @@ class Main(object):
self.notebook.set_current_page(page - 1)
def get_preferences(self):
rpc.context_reload()
try:
prefs = RPCExecute('model', 'res.user', 'get_preferences',
False)
except RPCException:
prefs = {}
threads = []
for target in (
common.ICONFACTORY.load_icons,
common.MODELACCESS.load_models,
common.VIEW_SEARCH.load_searches,
):
t = threading.Thread(target=target)
threads.append(t)
t.start()
for t in threads:
t.join()
if prefs and 'language_direction' in prefs:
translate.set_language_direction(prefs['language_direction'])
CONFIG['client.language_direction'] = \
prefs['language_direction']
self.sig_win_menu(prefs=prefs)
for action_id in prefs.get('actions', []):
Action.execute(action_id, {})
self.set_title(prefs.get('status_bar', ''))
if prefs and 'language' in prefs:
translate.setlang(prefs['language'], prefs.get('locale'))
if CONFIG['client.lang'] != prefs['language']:
self.set_menubar()
self.favorite_unset()
CONFIG['client.lang'] = prefs['language']
CONFIG.save()
def _set_preferences(prefs):
try:
prefs = prefs()
except RPCException:
prefs = {}
threads = []
for target in (
common.ICONFACTORY.load_icons,
common.MODELACCESS.load_models,
common.VIEW_SEARCH.load_searches,
):
t = threading.Thread(target=target)
threads.append(t)
t.start()
for t in threads:
t.join()
if prefs and 'language_direction' in prefs:
translate.set_language_direction(prefs['language_direction'])
CONFIG['client.language_direction'] = \
prefs['language_direction']
self.sig_win_menu(prefs=prefs)
for action_id in prefs.get('actions', []):
Action.execute(action_id, {})
self.set_title(prefs.get('status_bar', ''))
if prefs and 'language' in prefs:
translate.setlang(prefs['language'], prefs.get('locale'))
if CONFIG['client.lang'] != prefs['language']:
self.set_menubar()
self.favorite_unset()
CONFIG['client.lang'] = prefs['language']
CONFIG.save()
def _get_preferences():
RPCExecute('model', 'res.user', 'get_preferences', False,
callback=_set_preferences)
RPCContextReload(_get_preferences)
def sig_user_preferences(self, widget):
if not self.close_pages():
+5 -8
View File
@@ -410,7 +410,7 @@ class M2OField(CharField):
if not rec_name and value >= 0:
try:
result, = RPCExecute('model', self.attrs['relation'], 'read',
[value], ['rec_name'], main_iteration=False)
[value], ['rec_name'])
except RPCException:
return False
rec_name = result['rec_name'] or ''
@@ -603,8 +603,7 @@ class O2MField(CharField):
if field_names:
try:
fields.update(RPCExecute('model', self.attrs['relation'],
'fields_get', list(field_names),
main_iteration=False, context=context))
'fields_get', list(field_names), context=context))
except RPCException:
return
@@ -681,8 +680,7 @@ class O2MField(CharField):
if field_names:
try:
fields = RPCExecute('model', self.attrs['relation'],
'fields_get', list(field_names), main_iteration=False,
context=context)
'fields_get', list(field_names), context=context)
except RPCException:
return False
else:
@@ -855,7 +853,7 @@ class ReferenceField(CharField):
if not rec_name and ref_id >= 0:
try:
result, = RPCExecute('model', ref_model, 'read', [ref_id],
['rec_name'], main_iteration=False)
['rec_name'])
except RPCException:
return
rec_name = result['rec_name']
@@ -910,8 +908,7 @@ class BinaryField(CharField):
context = record.context_get()
try:
values, = RPCExecute('model', record.model_name, 'read',
[record.id], [self.name], main_iteration=False,
context=context)
[record.id], [self.name], context=context)
except RPCException:
return ''
_, filename = tempfile.mkstemp(prefix='tryton_')
+2 -2
View File
@@ -203,7 +203,7 @@ class Group(SignalEvent, list):
for fnct in self.on_write:
try:
res += RPCExecute('model', self.model_name, fnct, ids,
main_iteration=False, context=self.context)
context=self.context)
except RPCException:
return []
return list({}.fromkeys(res))
@@ -392,7 +392,7 @@ class Group(SignalEvent, list):
if len(new) and len(to_add):
try:
values = RPCExecute('model', self.model_name, 'default_get',
to_add.keys(), main_iteration=False, context=self.context)
to_add.keys(), context=self.context)
except RPCException:
return False
for record in new:
+12 -20
View File
@@ -118,8 +118,7 @@ class Record(SignalEvent):
exception = None
try:
values = RPCExecute('model', self.model_name, 'read',
id2record.keys(), fnames, context=ctx,
main_iteration=False)
id2record.keys(), fnames, context=ctx)
except RPCException, exception:
values = [{'id': x} for x in id2record]
default_values = dict((f, None) for f in fnames)
@@ -286,7 +285,7 @@ class Record(SignalEvent):
values = self._get_on_change_args(self.modified_fields)
try:
RPCExecute('model', self.model_name, 'pre_validate', values,
main_iteration=False, context=self.context_get())
context=self.context_get())
except RPCException:
return False
return True
@@ -297,8 +296,7 @@ class Record(SignalEvent):
value = self.get()
try:
res, = RPCExecute('model', self.model_name, 'create',
[value], main_iteration=False,
context=self.context_get())
[value], context=self.context_get())
except RPCException:
return False
old_id = self.id
@@ -312,8 +310,7 @@ class Record(SignalEvent):
context['_timestamp'] = self.get_timestamp()
try:
RPCExecute('model', self.model_name, 'write',
[self.id], value, main_iteration=False,
context=context)
[self.id], value, context=context)
except RPCException:
return False
self._loaded.clear()
@@ -346,7 +343,7 @@ class Record(SignalEvent):
reload_ids = list(reload_ids)
try:
RPCExecute('model', record.model_name, 'delete', list(record_ids),
main_iteration=False, context=ctx)
context=ctx)
except RPCException:
return False
if reload_ids:
@@ -357,8 +354,7 @@ class Record(SignalEvent):
if len(self.group.fields):
try:
vals = RPCExecute('model', self.model_name, 'default_get',
self.group.fields.keys(), main_iteration=False,
context=self.context_get())
self.group.fields.keys(), context=self.context_get())
except RPCException:
return
if (self.parent
@@ -380,8 +376,7 @@ class Record(SignalEvent):
def rec_name(self):
try:
return RPCExecute('model', self.model_name, 'read', [self.id],
['rec_name'], main_iteration=False,
context=self.context_get())[0]['rec_name']
['rec_name'], context=self.context_get())[0]['rec_name']
except RPCException:
return ''
@@ -532,8 +527,7 @@ class Record(SignalEvent):
args = self._get_on_change_args(attr)
try:
res = RPCExecute('model', self.model_name, 'on_change_' +
fieldname, args, main_iteration=False,
context=self.context_get())
fieldname, args, context=self.context_get())
except RPCException:
return
self.set_on_change(res)
@@ -564,8 +558,7 @@ class Record(SignalEvent):
if fieldnames:
try:
result = RPCExecute('model', self.model_name, 'on_change_with',
values, list(fieldnames), main_iteration=False,
context=self.context_get())
values, list(fieldnames), context=self.context_get())
except RPCException:
return
self.set_on_change(result)
@@ -576,7 +569,7 @@ class Record(SignalEvent):
try:
result = RPCExecute('model', self.model_name,
'on_change_with_' + fieldname, values,
main_iteration=False, context=self.context_get())
context=self.context_get())
except RPCException:
return
self.group.fields[fieldname].set_on_change(self, result)
@@ -594,8 +587,7 @@ class Record(SignalEvent):
args = self._get_on_change_args(autocomplete)
try:
res = RPCExecute('model', self.model_name, 'autocomplete_' +
fieldname, args, main_iteration=False,
context=self.context_get())
fieldname, args, context=self.context_get())
except RPCException:
# ensure res is a list
res = []
@@ -610,7 +602,7 @@ class Record(SignalEvent):
'search_count', [
('resource', '=',
'%s,%s' % (self.model_name, self.id)),
], main_iteration=False)
])
except RPCException:
return 0
return self.attachment_count
+68 -66
View File
@@ -3,17 +3,16 @@
import gtk
import pango
import gettext
import socket
from tryton.signal_event import SignalEvent
import tryton.rpc as rpc
import tryton.common as common
from tryton.gui.window.view_form.screen import Screen
from tryton.gui import Main
from tryton.exceptions import TrytonServerError, TrytonServerUnavailable
from tryton.exceptions import TrytonServerError
from tryton.gui.window.nomodal import NoModal
from tryton.common.button import Button
from tryton.common import RPCExecute, RPCException, TRYTON_ICON
from tryton.common import RPCExecute, RPCException, RPCContextReload
from tryton.common import TRYTON_ICON
_ = gettext.gettext
@@ -55,83 +54,85 @@ class Wizard(object):
self.email_print = email_print
self.email = email
self.context = context
try:
result = RPCExecute('wizard', action, 'create')
except RPCException:
return
self.session_id, self.start_state, self.end_state = result
self.state = self.start_state
self.process()
def callback(result):
try:
result = result()
except RPCException:
return
self.session_id, self.start_state, self.end_state = result
self.state = self.start_state
self.process()
RPCExecute('wizard', action, 'create', callback=callback)
def process(self):
from tryton.action import Action
if self.__processing or self.__waiting_response:
return
try:
self.__processing = True
while self.state != self.end_state:
ctx = self.context.copy()
ctx['active_id'] = self.id
ctx['active_ids'] = self.ids
ctx['active_model'] = self.model
ctx['action_id'] = self.action_id
if self.screen:
data = {
self.screen_state: self.screen.get_on_change_value(),
}
else:
data = {}
try:
result = RPCExecute('wizard', self.action, 'execute',
self.session_id, data, self.state, context=ctx)
except RPCException, rpc_exception:
if (not isinstance(rpc_exception.exception,
TrytonServerError)
or not self.screen):
self.state = self.end_state
break
self.__processing = True
if 'view' in result:
self.clean()
view = result['view']
self.update(view['fields_view'], view['defaults'],
view['buttons'])
self.screen_state = view['state']
self.__waiting_response = True
else:
ctx = self.context.copy()
ctx['active_id'] = self.id
ctx['active_ids'] = self.ids
ctx['active_model'] = self.model
ctx['action_id'] = self.action_id
if self.screen:
data = {
self.screen_state: self.screen.get_on_change_value(),
}
else:
data = {}
def callback(result):
try:
result = result()
except RPCException, rpc_exception:
if (not isinstance(rpc_exception.exception,
TrytonServerError)
or not self.screen):
self.state = self.end_state
self.end()
self.__processing = False
return
if 'actions' in result:
sensitive_widget = self.widget.get_toplevel()
if not 'view' in result:
self.end()
for action in result['actions']:
Action._exec_action(*action, context=ctx)
if (not 'view' in result
or (action[0]['type'] == 'ir.action.wizard'
and not sensitive_widget.props.sensitive)):
return
if self.__waiting_response:
break
if 'view' in result:
self.clean()
view = result['view']
self.update(view['fields_view'], view['defaults'],
view['buttons'])
self.screen_state = view['state']
self.__waiting_response = True
else:
self.state = self.end_state
if 'actions' in result:
sensitive_widget = self.widget.get_toplevel()
if not 'view' in result:
self.end()
for action in result['actions']:
Action._exec_action(*action, context=ctx)
if (not 'view' in result
or (action[0]['type'] == 'ir.action.wizard'
and not sensitive_widget.props.sensitive)):
self.__processing = False
return
if self.state == self.end_state:
self.end()
finally:
self.__processing = False
RPCExecute('wizard', self.action, 'execute', self.session_id, data,
self.state, context=ctx, callback=callback)
def destroy(self):
if self.screen:
self.screen.destroy()
def end(self):
try:
return RPCExecute('wizard', self.action, 'delete', self.session_id,
process_exception=False)
if self.action == 'ir.module.module.config_wizard':
rpc.context_reload()
Main.get_main().sig_win_menu()
except (TrytonServerError, socket.error, TrytonServerUnavailable):
pass
def end(self, callback=None):
RPCExecute('wizard', self.action, 'delete', self.session_id,
process_exception=False, callback=callback)
if self.action == 'ir.module.module.config_wizard':
RPCContextReload(Main.get_main().sig_win_menu)
def clean(self):
for widget in self.widget.get_children():
@@ -360,8 +361,9 @@ class WizardDialog(Wizard, NoModal):
dialog.screen.client_action(action)
def end(self):
action = super(WizardDialog, self).end()
self.destroy(action=action)
def callback(action):
self.destroy(action=action())
super(WizardDialog, self).end(callback=callback)
def close(self, widget, event=None):
if self.end_state in self.states:
-10
View File
@@ -130,16 +130,6 @@ def logout():
_KEYWORD_CACHE = {}
def context_reload():
global CONTEXT
try:
context = execute('model', 'res.user', 'get_preferences', True, {})
except Fault:
return
CONTEXT = {}
CONTEXT.update(context)
def _execute(blocking, *args):
global CONNECTION, _USER, _SESSION
if CONNECTION is None: