comparison browser/sat_browser/json.py @ 1124:28e3eb3bb217

files reorganisation and installation rework: - files have been reorganised to follow other SàT projects and usual Python organisation (no more "/src" directory) - VERSION file is now used, as for other SàT projects - replace the overcomplicated setup.py be a more sane one. Pyjamas part is not compiled anymore by setup.py, it must be done separatly - removed check for data_dir if it's empty - installation tested working in virtual env - libervia launching script is now in bin/libervia
author Goffi <goffi@goffi.org>
date Sat, 25 Aug 2018 17:59:48 +0200
parents src/browser/sat_browser/json.py@63a4b8fe9782
children 2af117bfe6cc
comparison
equal deleted inserted replaced
1123:63a4b8fe9782 1124:28e3eb3bb217
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2011-2018 Jérôme Poisson <goffi@goffi.org>
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
21 ### logging configuration ###
22 from sat.core.log import getLogger
23 log = getLogger(__name__)
24 ###
25
26 from pyjamas.Timer import Timer
27 from pyjamas import Window
28 from pyjamas import JSONService
29 import time
30 from sat_browser import main_panel
31
32 from sat_browser.constants import Const as C
33 import random
34
35
36 class LiberviaMethodProxy(object):
37 """This class manage calling for one method"""
38
39 def __init__(self, parent, method):
40 self._parent = parent
41 self._method = method
42
43 def call(self, *args, **kwargs):
44 """Method called when self._method attribue is used in JSON_PROXY_PARENT
45
46 This method manage callback/errback in kwargs, and profile(_key) removing
47 @param *args: positional arguments of self._method
48 @param **kwargs: keyword arguments of self._method
49 """
50 callback=kwargs.pop('callback', None)
51 errback=kwargs.pop('errback', None)
52
53 # as profile is linked to browser session and managed server side, we remove them
54 profile_removed = False
55 try:
56 kwargs['profile'] # FIXME: workaround for pyjamas bug: KeyError is not raised with del
57 del kwargs['profile']
58 profile_removed = True
59 except KeyError:
60 pass
61
62 try:
63 kwargs['profile_key'] # FIXME: workaround for pyjamas bug: KeyError is not raised iwith del
64 del kwargs['profile_key']
65 profile_removed = True
66 except KeyError:
67 pass
68
69 if not profile_removed and args:
70 # if profile was not in kwargs, there is most probably one in args
71 args = list(args)
72 assert isinstance(args[-1], basestring) # Detect when we want to remove a callback (or something else) instead of the profile
73 del args[-1]
74
75 if kwargs:
76 # kwargs should be empty here, we don't manage keyword arguments on bridge calls
77 log.error(u"kwargs is not empty after treatment on method call: kwargs={}".format(kwargs))
78
79 id_ = self._parent.callMethod(self._method, args)
80
81 # callback or errback are managed in parent LiberviaJsonProxy with call id
82 if callback is not None:
83 self._parent.cb[id_] = callback
84 if errback is not None:
85 self._parent.eb[id_] = errback
86
87
88 class LiberviaJsonProxy(JSONService.JSONService):
89
90 def __init__(self, url, methods):
91 self._serviceURL = url
92 self.methods = methods
93 JSONService.JSONService.__init__(self, url, self)
94 self.cb = {}
95 self.eb = {}
96 self._registerMethods(methods)
97
98 def _registerMethods(self, methods):
99 if methods:
100 for method in methods:
101 log.debug(u"Registering JSON method call [{}]".format(method))
102 setattr(self,
103 method,
104 getattr(LiberviaMethodProxy(self, method), 'call')
105 )
106
107 def callMethod(self, method, params, handler = None):
108 ret = super(LiberviaJsonProxy, self).callMethod(method, params, handler)
109 return ret
110
111 def call(self, method, cb, *args):
112 # FIXME: deprecated call method, must be removed once it's not used anymore
113 id_ = self.callMethod(method, args)
114 log.debug(u"call: method={} [id={}], args={}".format(method, id_, args))
115 if cb:
116 if isinstance(cb, tuple):
117 if len(cb) != 2:
118 log.error("tuple syntax for bridge.call is (callback, errback), aborting")
119 return
120 if cb[0] is not None:
121 self.cb[id_] = cb[0]
122 self.eb[id_] = cb[1]
123 else:
124 self.cb[id_] = cb
125
126 def onRemoteResponse(self, response, request_info):
127 try:
128 _cb = self.cb[request_info.id]
129 except KeyError:
130 pass
131 else:
132 _cb(response)
133 del self.cb[request_info.id]
134
135 try:
136 del self.eb[request_info.id]
137 except KeyError:
138 pass
139
140 def onRemoteError(self, code, errobj, request_info):
141 """def dump(obj):
142 print "\n\nDUMPING %s\n\n" % obj
143 for i in dir(obj):
144 print "%s: %s" % (i, getattr(obj,i))"""
145 try:
146 _eb = self.eb[request_info.id]
147 except KeyError:
148 if code != 0:
149 log.error("Internal server error")
150 """for o in code, error, request_info:
151 dump(o)"""
152 else:
153 if isinstance(errobj['message'], dict):
154 log.error(u"Error %s: %s" % (errobj['message']['faultCode'], errobj['message']['faultString']))
155 else:
156 log.error(u"%s" % errobj['message'])
157 else:
158 _eb((code, errobj))
159 del self.eb[request_info.id]
160
161 try:
162 del self.cb[request_info.id]
163 except KeyError:
164 pass
165
166
167 class RegisterCall(LiberviaJsonProxy):
168 def __init__(self):
169 LiberviaJsonProxy.__init__(self, "/register_api",
170 ["getSessionMetadata", "isConnected", "connect", "registerParams", "menusGet"])
171
172
173 class BridgeCall(LiberviaJsonProxy):
174 def __init__(self):
175 LiberviaJsonProxy.__init__(self, "/json_api",
176 ["getContacts", "addContact", "messageSend",
177 "psNodeDelete", "psRetractItem", "psRetractItems",
178 "mbSend", "mbRetract", "mbGet", "mbGetFromMany", "mbGetFromManyRTResult",
179 "mbGetFromManyWithComments", "mbGetFromManyWithCommentsRTResult",
180 "historyGet", "getPresenceStatuses", "joinMUC", "mucLeave", "mucGetRoomsJoined",
181 "inviteMUC", "launchTarotGame", "getTarotCardsPaths", "tarotGameReady",
182 "tarotGamePlayCards", "launchRadioCollective",
183 "getWaitingSub", "subscription", "delContact", "updateContact", "avatarGet",
184 "getEntityData", "getParamsUI", "asyncGetParamA", "setParam", "launchAction",
185 "disconnect", "chatStateComposing", "getNewAccountDomain",
186 "syntaxConvert", "getAccountDialogUI", "getMainResource", "getEntitiesData",
187 "getVersion", "getLiberviaVersion", "mucGetDefaultService", "getFeatures",
188 "namespacesGet",
189 ])
190
191 def __call__(self, *args, **kwargs):
192 return LiberviaJsonProxy.__call__(self, *args, **kwargs)
193
194 def getConfig(self, dummy1, dummy2): # FIXME
195 log.warning("getConfig is not implemeted in Libervia yet")
196 return ''
197
198 def isConnected(self, dummy, callback): # FIXME
199 log.warning("isConnected is not implemeted in Libervia as for now profile is connected if session is opened")
200 callback(True)
201
202 def encryptionPluginsGet(self, callback, errback):
203 """e2e encryption have no sense if made on backend, so we ignore this call"""
204 callback([])
205
206 def bridgeConnect(self, callback, errback):
207 callback()
208
209
210 class BridgeSignals(LiberviaJsonProxy):
211
212 def __init__(self, host):
213 self.host = host
214 self.retry_time = None
215 self.retry_nb = 0
216 self.retry_warning = None
217 self.retry_timer = None
218 LiberviaJsonProxy.__init__(self, "/json_signal_api",
219 ["getSignals"])
220 self._signals = {} # key: signal name, value: callback
221
222 def onRemoteResponse(self, response, request_info):
223 if self.retry_time:
224 log.info("Connection with server restablished")
225 self.retry_nb = 0
226 self.retry_time = None
227 LiberviaJsonProxy.onRemoteResponse(self, response, request_info)
228
229 def onRemoteError(self, code, errobj, request_info):
230 if errobj['message'] == 'Empty Response':
231 log.warning(u"Empty reponse bridgeSignal\ncode={}\nrequest_info: id={} method={} handler={}".format(code, request_info.id, request_info.method, request_info.handler))
232 # FIXME: to check/replace by a proper session end on disconnected signal
233 # Window.getLocation().reload() # XXX: reset page in case of session ended.
234 # FIXME: Should be done more properly without hard reload
235 LiberviaJsonProxy.onRemoteError(self, code, errobj, request_info)
236 #we now try to reconnect
237 if isinstance(errobj['message'], dict) and errobj['message']['faultCode'] == 0:
238 Window.alert('You are not allowed to connect to server')
239 else:
240 def _timerCb(dummy):
241 current = time.time()
242 if current > self.retry_time:
243 msg = "Trying to reconnect to server..."
244 log.info(msg)
245 self.retry_warning.showWarning("INFO", msg)
246 self.retry_timer.cancel()
247 self.retry_warning = self.retry_timer = None
248 self.getSignals(callback=self.signalHandler, profile=None)
249 else:
250 remaining = int(self.retry_time - current)
251 msg_html = u"Connection with server lost. Retrying in <strong>{}</strong> s".format(remaining)
252 self.retry_warning.showWarning("WARNING", msg_html, None)
253
254 if self.retry_nb < 3:
255 retry_delay = 1
256 elif self.retry_nb < 10:
257 retry_delay = random.randint(1,10)
258 else:
259 retry_delay = random.randint(1,60)
260 self.retry_nb += 1
261 log.warning(u"Lost connection, trying to reconnect in {} s (try #{})".format(retry_delay, self.retry_nb))
262 self.retry_time = time.time() + retry_delay
263 self.retry_warning = main_panel.WarningPopup()
264 self.retry_timer = Timer(notify=_timerCb)
265 self.retry_timer.scheduleRepeating(1000)
266 _timerCb(None)
267
268 def register_signal(self, name, callback, with_profile=True):
269 """Register a signal
270
271 @param: name of the signal to register
272 @param callback: method to call
273 @param with_profile: True if the original bridge method need a profile
274 """
275 log.debug(u"Registering signal {}".format(name))
276 if name in self._signals:
277 log.error(u"Trying to register and already registered signal ({})".format(name))
278 else:
279 self._signals[name] = (callback, with_profile)
280
281 def signalHandler(self, signal_data):
282 self.getSignals(callback=self.signalHandler, profile=None)
283 if len(signal_data) == 1:
284 signal_data.append([])
285 log.debug(u"Got signal ==> name: %s, params: %s" % (signal_data[0], signal_data[1]))
286 name, args = signal_data
287 try:
288 callback, with_profile = self._signals[name]
289 except KeyError:
290 log.warning(u"Ignoring {} signal: no handler registered !".format(name))
291 return
292 if with_profile:
293 args.append(C.PROF_KEY_NONE)
294 if not self.host._profile_plugged:
295 log.debug("Profile is not plugged, we cache the signal")
296 self.host.signals_cache[C.PROF_KEY_NONE].append((name, callback, args, {}))
297 else:
298 callback(*args)