comparison src/browser/libervia_main.py @ 449:981ed669d3b3

/!\ reorganize all the file hierarchy, move the code and launching script to src: - browser_side --> src/browser - public --> src/browser_side/public - libervia.py --> src/browser/libervia_main.py - libervia_server --> src/server - libervia_server/libervia.sh --> src/libervia.sh - twisted --> src/twisted - new module src/common - split constants.py in 3 files: - src/common/constants.py - src/browser/constants.py - src/server/constants.py - output --> html (generated by pyjsbuild during the installation) - new option/parameter "data_dir" (-d) to indicates the directory containing html and server_css - setup.py installs libervia to the following paths: - src/common --> <LIB>/libervia/common - src/server --> <LIB>/libervia/server - src/twisted --> <LIB>/twisted - html --> <SHARE>/libervia/html - server_side --> <SHARE>libervia/server_side - LIBERVIA_INSTALL environment variable takes 2 new options with prompt confirmation: - clean: remove previous installation directories - purge: remove building and previous installation directories You may need to update your sat.conf and/or launching script to update the following options/parameters: - ssl_certificate - data_dir
author souliane <souliane@mailoo.org>
date Tue, 20 May 2014 06:41:16 +0200
parents libervia.py@17259c2ff96f
children 1a0cec9b0f1e
comparison
equal deleted inserted replaced
448:14c35f7f1ef5 449:981ed669d3b3
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2011, 2012, 2013, 2014 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 import pyjd # this is dummy in pyjs
21
22 ### logging configuration ###
23 import logging
24 logging.configure()
25 from sat.core.log import getLogger
26 log = getLogger(__name__)
27 ###
28
29 from sat_frontends.tools.misc import InputHistory
30 from sat_frontends.tools.strings import getURLParams
31 from sat.core.i18n import _
32
33 from pyjamas.ui.RootPanel import RootPanel
34 from pyjamas.ui.HTML import HTML
35 from pyjamas.ui.KeyboardListener import KEY_ESCAPE
36 from pyjamas.Timer import Timer
37 from pyjamas import Window, DOM
38 from pyjamas.JSONService import JSONProxy
39
40 from register import RegisterBox
41 from contact import ContactPanel
42 from base_widget import WidgetsPanel
43 from panels import MicroblogItem
44 import panels, dialog
45 from jid import JID
46 from xmlui import XMLUI
47 from html_tools import html_sanitize
48 from notification import Notification
49
50 from constants import Const as C
51
52
53 MAX_MBLOG_CACHE = 500 # Max microblog entries kept in memories
54
55 # Set to true to not create a new LiberviaWidget when a similar one
56 # already exist (i.e. a chat panel with the same target). Instead
57 # the existing widget will be eventually removed from its parent
58 # and added to new WidgetsPanel, or replaced to the expected
59 # position if the previous and the new parent are the same.
60 REUSE_EXISTING_LIBERVIA_WIDGETS = True
61
62
63 class LiberviaJsonProxy(JSONProxy):
64 def __init__(self, *args, **kwargs):
65 JSONProxy.__init__(self, *args, **kwargs)
66 self.handler = self
67 self.cb = {}
68 self.eb = {}
69
70 def call(self, method, cb, *args):
71 _id = self.callMethod(method, args)
72 if cb:
73 if isinstance(cb, tuple):
74 if len(cb) != 2:
75 log.error("tuple syntax for bridge.call is (callback, errback), aborting")
76 return
77 if cb[0] is not None:
78 self.cb[_id] = cb[0]
79 self.eb[_id] = cb[1]
80 else:
81 self.cb[_id] = cb
82
83 def onRemoteResponse(self, response, request_info):
84 if request_info.id in self.cb:
85 _cb = self.cb[request_info.id]
86 # if isinstance(_cb, tuple):
87 # #we have arguments attached to the callback
88 # #we send them after the answer
89 # callback, args = _cb
90 # callback(response, *args)
91 # else:
92 # #No additional argument, we call directly the callback
93 _cb(response)
94 del self.cb[request_info.id]
95 if request_info.id in self.eb:
96 del self.eb[request_info.id]
97
98 def onRemoteError(self, code, errobj, request_info):
99 """def dump(obj):
100 print "\n\nDUMPING %s\n\n" % obj
101 for i in dir(obj):
102 print "%s: %s" % (i, getattr(obj,i))"""
103 if request_info.id in self.eb:
104 _eb = self.eb[request_info.id]
105 _eb((code, errobj))
106 del self.cb[request_info.id]
107 del self.eb[request_info.id]
108 else:
109 if code != 0:
110 log.error("Internal server error")
111 """for o in code, error, request_info:
112 dump(o)"""
113 else:
114 if isinstance(errobj['message'], dict):
115 log.error("Error %s: %s" % (errobj['message']['faultCode'], errobj['message']['faultString']))
116 else:
117 log.error("%s" % errobj['message'])
118
119
120 class RegisterCall(LiberviaJsonProxy):
121 def __init__(self):
122 LiberviaJsonProxy.__init__(self, "/register_api",
123 ["isRegistered", "isConnected", "connect", "registerParams", "getMenus"])
124
125
126 class BridgeCall(LiberviaJsonProxy):
127 def __init__(self):
128 LiberviaJsonProxy.__init__(self, "/json_api",
129 ["getContacts", "addContact", "sendMessage", "sendMblog", "sendMblogComment",
130 "getLastMblogs", "getMassiveLastMblogs", "getMblogComments", "getProfileJid",
131 "getHistory", "getPresenceStatuses", "joinMUC", "mucLeave", "getRoomsJoined",
132 "inviteMUC", "launchTarotGame", "getTarotCardsPaths", "tarotGameReady",
133 "tarotGamePlayCards", "launchRadioCollective", "getMblogs", "getMblogsWithComments",
134 "getWaitingSub", "subscription", "delContact", "updateContact", "getCard",
135 "getEntityData", "getParamsUI", "asyncGetParamA", "setParam", "launchAction",
136 "disconnect", "chatStateComposing", "getNewAccountDomain", "confirmationAnswer",
137 "syntaxConvert", "getAccountDialogUI",
138 ])
139
140
141 class BridgeSignals(LiberviaJsonProxy):
142 RETRY_BASE_DELAY = 1000
143
144 def __init__(self, host):
145 self.host = host
146 self.retry_delay = self.RETRY_BASE_DELAY
147 LiberviaJsonProxy.__init__(self, "/json_signal_api",
148 ["getSignals"])
149
150 def onRemoteResponse(self, response, request_info):
151 self.retry_delay = self.RETRY_BASE_DELAY
152 LiberviaJsonProxy.onRemoteResponse(self, response, request_info)
153
154 def onRemoteError(self, code, errobj, request_info):
155 if errobj['message'] == 'Empty Response':
156 Window.getLocation().reload() # XXX: reset page in case of session ended.
157 # FIXME: Should be done more properly without hard reload
158 LiberviaJsonProxy.onRemoteError(self, code, errobj, request_info)
159 #we now try to reconnect
160 if isinstance(errobj['message'], dict) and errobj['message']['faultCode'] == 0:
161 Window.alert('You are not allowed to connect to server')
162 else:
163 def _timerCb(timer):
164 self.host.bridge_signals.call('getSignals', self.host._getSignalsCB)
165 Timer(notify=_timerCb).schedule(self.retry_delay)
166 self.retry_delay *= 2
167
168
169 class SatWebFrontend(InputHistory):
170 def onModuleLoad(self):
171 log.info("============ onModuleLoad ==============")
172 panels.ChatPanel.registerClass()
173 panels.MicroblogPanel.registerClass()
174 self.whoami = None
175 self._selected_listeners = set()
176 self.bridge = BridgeCall()
177 self.bridge_signals = BridgeSignals(self)
178 self.uni_box = None
179 self.status_panel = HTML('<br />')
180 self.contact_panel = ContactPanel(self)
181 self.panel = panels.MainPanel(self)
182 self.discuss_panel = self.panel.discuss_panel
183 self.tab_panel = self.panel.tab_panel
184 self.tab_panel.addTabListener(self)
185 self.libervia_widgets = set() # keep track of all actives LiberviaWidgets
186 self.room_list = [] # list of rooms
187 self.mblog_cache = [] # used to keep our own blog entries in memory, to show them in new mblog panel
188 self.avatars_cache = {} # keep track of jid's avatar hash (key=jid, value=file)
189 self._register_box = None
190 RootPanel().add(self.panel)
191 self.notification = Notification()
192 DOM.addEventPreview(self)
193 self._register = RegisterCall()
194 self._register.call('getMenus', self.panel.menu.createMenus)
195 self._register.call('registerParams', None)
196 self._register.call('isRegistered', self._isRegisteredCB)
197 self.initialised = False
198 self.init_cache = [] # used to cache events until initialisation is done
199 # define here the parameters that have an incidende to UI refresh
200 self.params_ui = {"unibox": {"name": C.ENABLE_UNIBOX_PARAM,
201 "category": C.ENABLE_UNIBOX_KEY,
202 "cast": lambda value: value == 'true',
203 "value": None
204 }
205 }
206
207 def addSelectedListener(self, callback):
208 self._selected_listeners.add(callback)
209
210 def getSelected(self):
211 wid = self.tab_panel.getCurrentPanel()
212 if not isinstance(wid, WidgetsPanel):
213 log.error("Tab widget is not a WidgetsPanel, can't get selected widget")
214 return None
215 return wid.selected
216
217 def setSelected(self, widget):
218 """Define the selected widget"""
219 widgets_panel = self.tab_panel.getCurrentPanel()
220 if not isinstance(widgets_panel, WidgetsPanel):
221 return
222
223 selected = widgets_panel.selected
224
225 if selected == widget:
226 return
227
228 if selected:
229 selected.removeStyleName('selected_widget')
230
231 widgets_panel.selected = widget
232
233 if widget:
234 widgets_panel.selected.addStyleName('selected_widget')
235
236 for callback in self._selected_listeners:
237 callback(widget)
238
239 def resize(self):
240 """Resize elements"""
241 Window.onResize()
242
243 def onBeforeTabSelected(self, sender, tab_index):
244 return True
245
246 def onTabSelected(self, sender, tab_index):
247 selected = self.getSelected()
248 for callback in self._selected_listeners:
249 callback(selected)
250
251 def onEventPreview(self, event):
252 if event.type in ["keydown", "keypress", "keyup"] and event.keyCode == KEY_ESCAPE:
253 #needed to prevent request cancellation in Firefox
254 event.preventDefault()
255 return True
256
257 def getAvatar(self, jid_str):
258 """Return avatar of a jid if in cache, else ask for it"""
259 def dataReceived(result):
260 if 'avatar' in result:
261 self._entityDataUpdatedCb(jid_str, 'avatar', result['avatar'])
262 else:
263 self.bridge.call("getCard", None, jid_str)
264
265 def avatarError(error_data):
266 # The jid is maybe not in our roster, we ask for the VCard
267 self.bridge.call("getCard", None, jid_str)
268
269 if jid_str not in self.avatars_cache:
270 self.bridge.call('getEntityData', (dataReceived, avatarError), jid_str, ['avatar'])
271 self.avatars_cache[jid_str] = "/media/icons/tango/emotes/64/face-plain.png"
272 return self.avatars_cache[jid_str]
273
274 def registerWidget(self, wid):
275 log.debug("Registering %s" % wid.getDebugName())
276 self.libervia_widgets.add(wid)
277
278 def unregisterWidget(self, wid):
279 try:
280 self.libervia_widgets.remove(wid)
281 except KeyError:
282 log.warning('trying to remove a non registered Widget: %s' % wid.getDebugName())
283
284 def refresh(self):
285 """Refresh the general display."""
286 self.panel.refresh()
287 if self.params_ui['unibox']['value']:
288 self.uni_box = self.panel.unibox_panel.unibox
289 else:
290 self.uni_box = None
291 for lib_wid in self.libervia_widgets:
292 lib_wid.refresh()
293 self.resize()
294
295 def addTab(self, label, wid, select=True):
296 """Create a new tab and eventually add a widget in
297 @param label: label of the tab
298 @param wid: LiberviaWidget to add
299 @param select: True to select the added tab
300 """
301 widgets_panel = WidgetsPanel(self)
302 self.tab_panel.add(widgets_panel, label)
303 widgets_panel.addWidget(wid)
304 if select:
305 self.tab_panel.selectTab(self.tab_panel.getWidgetCount() - 1)
306 return widgets_panel
307
308 def addWidget(self, wid, tab_index=None):
309 """ Add a widget at the bottom of the current or specified tab
310 @param wid: LiberviaWidget to add
311 @param tab_index: index of the tab to add the widget to"""
312 if tab_index is None or tab_index < 0 or tab_index >= self.tab_panel.getWidgetCount():
313 panel = self.tab_panel.getCurrentPanel()
314 else:
315 panel = self.tab_panel.tabBar.getTabWidget(tab_index)
316 panel.addWidget(wid)
317
318 def displayNotification(self, title, body):
319 self.notification.notify(title, body)
320
321 def _isRegisteredCB(self, result):
322 registered, warning = result
323 if not registered:
324 self._register_box = RegisterBox(self.logged)
325 self._register_box.centerBox()
326 self._register_box.show()
327 if warning:
328 dialog.InfoDialog(_('Security warning'), warning).show()
329 self._tryAutoConnect(skip_validation=not not warning)
330 else:
331 self._register.call('isConnected', self._isConnectedCB)
332
333 def _isConnectedCB(self, connected):
334 if not connected:
335 self._register.call('connect', lambda x: self.logged())
336 else:
337 self.logged()
338
339 def logged(self):
340 if self._register_box:
341 self._register_box.hide()
342 del self._register_box # don't work if self._register_box is None
343
344 # display the real presence status panel
345 self.panel.header.remove(self.status_panel)
346 self.status_panel = panels.PresenceStatusPanel(self)
347 self.panel.header.add(self.status_panel)
348
349 #it's time to fill the page
350 self.bridge.call('getContacts', self._getContactsCB)
351 self.bridge.call('getParamsUI', self._getParamsUICB)
352 self.bridge_signals.call('getSignals', self._getSignalsCB)
353 #We want to know our own jid
354 self.bridge.call('getProfileJid', self._getProfileJidCB)
355
356 def domain_cb(value):
357 self._defaultDomain = value
358 log.info("new account domain: %s" % value)
359
360 def domain_eb(value):
361 self._defaultDomain = "libervia.org"
362
363 self.bridge.call("getNewAccountDomain", (domain_cb, domain_eb))
364 self.discuss_panel.addWidget(panels.MicroblogPanel(self, []))
365
366 # get ui params and refresh the display
367 count = 0 # used to do something similar to DeferredList
368
369 def params_ui_cb(param, value=None):
370 count += 1
371 refresh = count == len(self.params_ui)
372 self._paramUpdate(param['name'], value, param['category'], refresh)
373 for param in self.params_ui:
374 self.bridge.call('asyncGetParamA', lambda value: params_ui_cb(self.params_ui[param], value),
375 self.params_ui[param]['name'], self.params_ui[param]['category'])
376
377 def _tryAutoConnect(self, skip_validation=False):
378 """This method retrieve the eventual URL parameters to auto-connect the user.
379 @param skip_validation: if True, set the form values but do not validate it
380 """
381 params = getURLParams(Window.getLocation().getSearch())
382 if "login" in params:
383 self._register_box._form.login_box.setText(params["login"])
384 self._register_box._form.login_pass_box.setFocus(True)
385 if "passwd" in params:
386 # try to connect
387 self._register_box._form.login_pass_box.setText(params["passwd"])
388 if not skip_validation:
389 self._register_box._form.onLogin(None)
390 return True
391 else:
392 # this would eventually set the browser saved password
393 Timer(5, lambda: self._register_box._form.login_pass_box.setFocus(True))
394
395 def _actionCb(self, data):
396 if not data:
397 # action was a one shot, nothing to do
398 pass
399 elif "xmlui" in data:
400 ui = XMLUI(self, xml_data = data['xmlui'])
401 options = ['NO_CLOSE'] if ui.type == 'form' else []
402 _dialog = dialog.GenericDialog(ui.title, ui, options=options)
403 ui.setCloseCb(_dialog.close)
404 _dialog.show()
405 else:
406 dialog.InfoDialog("Error",
407 "Unmanaged action result", Width="400px").center()
408
409 def _actionEb(self, err_data):
410 err_code, err_obj = err_data
411 dialog.InfoDialog("Error",
412 str(err_obj), Width="400px").center()
413
414 def launchAction(self, callback_id, data):
415 """ Launch a dynamic action
416 @param callback_id: id of the action to launch
417 @param data: data needed only for certain actions
418
419 """
420 if data is None:
421 data = {}
422 self.bridge.call('launchAction', (self._actionCb, self._actionEb), callback_id, data)
423
424 def _getContactsCB(self, contacts_data):
425 for contact in contacts_data:
426 jid, attributes, groups = contact
427 self._newContactCb(jid, attributes, groups)
428
429 def _getSignalsCB(self, signal_data):
430 self.bridge_signals.call('getSignals', self._getSignalsCB)
431 log.debug("Got signal ==> name: %s, params: %s" % (signal_data[0], signal_data[1]))
432 name, args = signal_data
433 if name == 'personalEvent':
434 self._personalEventCb(*args)
435 elif name == 'newMessage':
436 self._newMessageCb(*args)
437 elif name == 'presenceUpdate':
438 self._presenceUpdateCb(*args)
439 elif name == 'paramUpdate':
440 self._paramUpdate(*args)
441 elif name == 'roomJoined':
442 self._roomJoinedCb(*args)
443 elif name == 'roomLeft':
444 self._roomLeftCb(*args)
445 elif name == 'roomUserJoined':
446 self._roomUserJoinedCb(*args)
447 elif name == 'roomUserLeft':
448 self._roomUserLeftCb(*args)
449 elif name == 'roomUserChangedNick':
450 self._roomUserChangedNickCb(*args)
451 elif name == 'askConfirmation':
452 self._askConfirmation(*args)
453 elif name == 'newAlert':
454 self._newAlert(*args)
455 elif name == 'tarotGamePlayers':
456 self._tarotGameStartedCb(True, *args)
457 elif name == 'tarotGameStarted':
458 self._tarotGameStartedCb(False, *args)
459 elif name == 'tarotGameNew' or \
460 name == 'tarotGameChooseContrat' or \
461 name == 'tarotGameShowCards' or \
462 name == 'tarotGameInvalidCards' or \
463 name == 'tarotGameCardsPlayed' or \
464 name == 'tarotGameYourTurn' or \
465 name == 'tarotGameScore':
466 self._tarotGameGenericCb(name, args[0], args[1:])
467 elif name == 'radiocolPlayers':
468 self._radioColStartedCb(True, *args)
469 elif name == 'radiocolStarted':
470 self._radioColStartedCb(False, *args)
471 elif name == 'radiocolPreload':
472 self._radioColGenericCb(name, args[0], args[1:])
473 elif name == 'radiocolPlay':
474 self._radioColGenericCb(name, args[0], args[1:])
475 elif name == 'radiocolNoUpload':
476 self._radioColGenericCb(name, args[0], args[1:])
477 elif name == 'radiocolUploadOk':
478 self._radioColGenericCb(name, args[0], args[1:])
479 elif name == 'radiocolSongRejected':
480 self._radioColGenericCb(name, args[0], args[1:])
481 elif name == 'subscribe':
482 self._subscribeCb(*args)
483 elif name == 'contactDeleted':
484 self._contactDeletedCb(*args)
485 elif name == 'newContact':
486 self._newContactCb(*args)
487 elif name == 'entityDataUpdated':
488 self._entityDataUpdatedCb(*args)
489 elif name == 'chatStateReceived':
490 self._chatStateReceivedCb(*args)
491
492 def _getParamsUICB(self, xmlui):
493 """Hide the parameters item if there's nothing to display"""
494 if not xmlui:
495 self.panel.menu.removeItemParams()
496
497 def _ownBlogsFills(self, mblogs):
498 #put our own microblogs in cache, then fill all panels with them
499 for publisher in mblogs:
500 for mblog in mblogs[publisher]:
501 if not mblog.has_key('content'):
502 log.warning("No content found in microblog [%s]" % mblog)
503 continue
504 if mblog.has_key('groups'):
505 _groups = set(mblog['groups'].split() if mblog['groups'] else [])
506 else:
507 _groups = None
508 mblog_entry = MicroblogItem(mblog)
509 self.mblog_cache.append((_groups, mblog_entry))
510
511 if len(self.mblog_cache) > MAX_MBLOG_CACHE:
512 del self.mblog_cache[0:len(self.mblog_cache - MAX_MBLOG_CACHE)]
513 for lib_wid in self.libervia_widgets:
514 if isinstance(lib_wid, panels.MicroblogPanel):
515 self.FillMicroblogPanel(lib_wid)
516 self.initialised = True # initialisation phase is finished here
517 for event_data in self.init_cache: # so we have to send all the cached events
518 self._personalEventCb(*event_data)
519 del self.init_cache
520
521 def _getProfileJidCB(self, jid):
522 self.whoami = JID(jid)
523 #we can now ask our status
524 self.bridge.call('getPresenceStatuses', self._getPresenceStatusesCb)
525 #the rooms where we are
526 self.bridge.call('getRoomsJoined', self._getRoomsJoinedCb)
527 #and if there is any subscription request waiting for us
528 self.bridge.call('getWaitingSub', self._getWaitingSubCb)
529 #we fill the panels already here
530 for lib_wid in self.libervia_widgets:
531 if isinstance(lib_wid, panels.MicroblogPanel):
532 if lib_wid.accept_all():
533 self.bridge.call('getMassiveLastMblogs', lib_wid.massiveInsert, 'ALL', [], 10)
534 else:
535 self.bridge.call('getMassiveLastMblogs', lib_wid.massiveInsert, 'GROUP', lib_wid.accepted_groups, 10)
536
537 #we ask for our own microblogs:
538 self.bridge.call('getMassiveLastMblogs', self._ownBlogsFills, 'JID', [self.whoami.bare], 10)
539
540 ## Signals callbacks ##
541
542 def _personalEventCb(self, sender, event_type, data):
543 if not self.initialised:
544 self.init_cache.append((sender, event_type, data))
545 return
546 sender = JID(sender).bare
547 if event_type == "MICROBLOG":
548 if not 'content' in data:
549 log.warning("No content found in microblog data")
550 return
551 if 'groups' in data:
552 _groups = set(data['groups'].split() if data['groups'] else [])
553 else:
554 _groups = None
555 mblog_entry = MicroblogItem(data)
556
557 for lib_wid in self.libervia_widgets:
558 if isinstance(lib_wid, panels.MicroblogPanel):
559 self.addBlogEntry(lib_wid, sender, _groups, mblog_entry)
560
561 if sender == self.whoami.bare:
562 found = False
563 for index in xrange(0, len(self.mblog_cache)):
564 entry = self.mblog_cache[index]
565 if entry[1].id == mblog_entry.id:
566 # replace existing entry
567 self.mblog_cache.remove(entry)
568 self.mblog_cache.insert(index, (_groups, mblog_entry))
569 found = True
570 break
571 if not found:
572 self.mblog_cache.append((_groups, mblog_entry))
573 if len(self.mblog_cache) > MAX_MBLOG_CACHE:
574 del self.mblog_cache[0:len(self.mblog_cache - MAX_MBLOG_CACHE)]
575 elif event_type == 'MICROBLOG_DELETE':
576 for lib_wid in self.libervia_widgets:
577 if isinstance(lib_wid, panels.MicroblogPanel):
578 lib_wid.removeEntry(data['type'], data['id'])
579 log.debug("%s %s %s" % (self.whoami.bare, sender, data['type']))
580
581 if sender == self.whoami.bare and data['type'] == 'main_item':
582 for index in xrange(0, len(self.mblog_cache)):
583 entry = self.mblog_cache[index]
584 if entry[1].id == data['id']:
585 self.mblog_cache.remove(entry)
586 break
587
588 def addBlogEntry(self, mblog_panel, sender, _groups, mblog_entry):
589 """Check if an entry can go in MicroblogPanel and add to it
590 @param mblog_panel: MicroblogPanel instance
591 @param sender: jid of the entry sender
592 @param _groups: groups which can receive this entry
593 @param mblog_entry: MicroblogItem instance"""
594 if mblog_entry.type == "comment" or mblog_panel.isJidAccepted(sender) or (_groups == None and self.whoami and sender == self.whoami.bare) \
595 or (_groups and _groups.intersection(mblog_panel.accepted_groups)):
596 mblog_panel.addEntry(mblog_entry)
597
598 def FillMicroblogPanel(self, mblog_panel):
599 """Fill a microblog panel with entries in cache
600 @param mblog_panel: MicroblogPanel instance
601 """
602 #XXX: only our own entries are cached
603 for cache_entry in self.mblog_cache:
604 _groups, mblog_entry = cache_entry
605 self.addBlogEntry(mblog_panel, self.whoami.bare, *cache_entry)
606
607 def getEntityMBlog(self, entity):
608 log.info("geting mblog for entity [%s]" % (entity,))
609 for lib_wid in self.libervia_widgets:
610 if isinstance(lib_wid, panels.MicroblogPanel):
611 if lib_wid.isJidAccepted(entity):
612 self.bridge.call('getMassiveLastMblogs', lib_wid.massiveInsert, 'JID', [entity], 10)
613
614 def getLiberviaWidget(self, class_, entity, ignoreOtherTabs=True):
615 """Get the corresponding panel if it exists.
616 @param class_: class of the panel (ChatPanel, MicroblogPanel...)
617 @param entity: polymorphic parameter, see class_.matchEntity.
618 @param ignoreOtherTabs: if True, the widgets that are not
619 contained by the currently selected tab will be ignored
620 @return: the existing widget that has been found or None."""
621 selected_tab = self.tab_panel.getCurrentPanel()
622 for lib_wid in self.libervia_widgets:
623 parent = lib_wid.getWidgetsPanel(verbose=False)
624 if parent is None or (ignoreOtherTabs and parent != selected_tab):
625 # do not return a widget that is not in the currently selected tab
626 continue
627 if isinstance(lib_wid, class_):
628 try:
629 if lib_wid.matchEntity(entity):
630 log.debug("existing widget found: %s" % lib_wid.getDebugName())
631 return lib_wid
632 except AttributeError as e:
633 e.stack_list()
634 return None
635 return None
636
637 def getOrCreateLiberviaWidget(self, class_, entity, select=True, new_tab=None):
638 """Get the matching LiberviaWidget if it exists, or create a new one.
639 @param class_: class of the panel (ChatPanel, MicroblogPanel...)
640 @param entity: polymorphic parameter, see class_.matchEntity.
641 @param select: if True, select the widget that has been found or created
642 @param new_tab: if not None, a widget which is created is created in
643 a new tab. In that case new_tab is a unicode to label that new tab.
644 If new_tab is not None and a widget is found, no tab is created.
645 @return: the newly created wigdet if REUSE_EXISTING_LIBERVIA_WIDGETS
646 is set to False or if the widget has not been found, the existing
647 widget that has been found otherwise."""
648 lib_wid = None
649 tab = None
650 if REUSE_EXISTING_LIBERVIA_WIDGETS:
651 lib_wid = self.getLiberviaWidget(class_, entity, new_tab is None)
652 if lib_wid is None: # create a new widget
653 lib_wid = class_.createPanel(self, entity[0] if isinstance(entity, tuple) else entity)
654 if new_tab is None:
655 self.addWidget(lib_wid)
656 else:
657 tab = self.addTab(new_tab, lib_wid, False)
658 else: # reuse existing widget
659 tab = lib_wid.getWidgetsPanel(verbose=False)
660 if new_tab is None:
661 if tab is not None:
662 tab.removeWidget(lib_wid)
663 self.addWidget(lib_wid)
664 if select:
665 if new_tab is not None:
666 self.tab_panel.selectTab(tab)
667 # must be done after the widget is added,
668 # for example to scroll to the bottom
669 self.setSelected(lib_wid)
670 lib_wid.refresh()
671 return lib_wid
672
673 def _newMessageCb(self, from_jid, msg, msg_type, to_jid, extra):
674 _from = JID(from_jid)
675 _to = JID(to_jid)
676 other = _to if _from.bare == self.whoami.bare else _from
677 lib_wid = self.getLiberviaWidget(panels.ChatPanel, other, ignoreOtherTabs=False)
678 self.displayNotification(_from, msg)
679 if lib_wid is not None:
680 lib_wid.printMessage(_from, msg, extra)
681 else:
682 # The message has not been shown, we must indicate it
683 self.contact_panel.setContactMessageWaiting(other.bare, True)
684
685 def _presenceUpdateCb(self, entity, show, priority, statuses):
686 entity_jid = JID(entity)
687 if self.whoami and self.whoami == entity_jid: # XXX: QnD way to get our presence/status
688 self.status_panel.setPresence(show)
689 if statuses:
690 self.status_panel.setStatus(statuses.values()[0])
691 else:
692 self.contact_panel.setConnected(entity_jid.bare, entity_jid.resource, show, priority, statuses)
693
694 def _roomJoinedCb(self, room_jid, room_nicks, user_nick):
695 _target = JID(room_jid)
696 if _target not in self.room_list:
697 self.room_list.append(_target)
698 chat_panel = panels.ChatPanel(self, _target, type_='group')
699 chat_panel.setUserNick(user_nick)
700 if _target.node.startswith('sat_tarot_'): #XXX: it's not really beautiful, but it works :)
701 self.addTab("Tarot", chat_panel)
702 elif _target.node.startswith('sat_radiocol_'):
703 self.addTab("Radio collective", chat_panel)
704 else:
705 self.addTab(_target.node, chat_panel)
706 chat_panel.setPresents(room_nicks)
707 chat_panel.historyPrint()
708 chat_panel.refresh()
709
710 def _roomLeftCb(self, room_jid, room_nicks, user_nick):
711 # FIXME: room_list contains JID instances so why MUST we do
712 # 'remove(room_jid)' and not 'remove(JID(room_jid))' ????!!
713 # This looks like a pyjamas bug --> check/report
714 try:
715 self.room_list.remove(room_jid)
716 except KeyError:
717 pass
718
719 def _roomUserJoinedCb(self, room_jid_s, user_nick, user_data):
720 for lib_wid in self.libervia_widgets:
721 if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
722 lib_wid.userJoined(user_nick, user_data)
723
724 def _roomUserLeftCb(self, room_jid_s, user_nick, user_data):
725 for lib_wid in self.libervia_widgets:
726 if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
727 lib_wid.userLeft(user_nick, user_data)
728
729 def _roomUserChangedNickCb(self, room_jid_s, old_nick, new_nick):
730 """Called when an user joined a MUC room"""
731 for lib_wid in self.libervia_widgets:
732 if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
733 lib_wid.changeUserNick(old_nick, new_nick)
734
735 def _tarotGameStartedCb(self, waiting, room_jid_s, referee, players):
736 for lib_wid in self.libervia_widgets:
737 if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
738 lib_wid.startGame("Tarot", waiting, referee, players)
739
740 def _tarotGameGenericCb(self, event_name, room_jid_s, args):
741 for lib_wid in self.libervia_widgets:
742 if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
743 getattr(lib_wid.getGame("Tarot"), event_name)(*args)
744
745 def _radioColStartedCb(self, waiting, room_jid_s, referee, players, queue_data):
746 for lib_wid in self.libervia_widgets:
747 if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
748 lib_wid.startGame("RadioCol", waiting, referee, players, queue_data)
749
750 def _radioColGenericCb(self, event_name, room_jid_s, args):
751 for lib_wid in self.libervia_widgets:
752 if isinstance(lib_wid, panels.ChatPanel) and lib_wid.type == 'group' and lib_wid.target.bare == room_jid_s:
753 getattr(lib_wid.getGame("RadioCol"), event_name)(*args)
754
755 def _getPresenceStatusesCb(self, presence_data):
756 for entity in presence_data:
757 for resource in presence_data[entity]:
758 args = presence_data[entity][resource]
759 self._presenceUpdateCb("%s/%s" % (entity, resource), *args)
760
761 def _getRoomsJoinedCb(self, room_data):
762 for room in room_data:
763 self._roomJoinedCb(*room)
764
765 def _getWaitingSubCb(self, waiting_sub):
766 for sub in waiting_sub:
767 self._subscribeCb(waiting_sub[sub], sub)
768
769 def _subscribeCb(self, sub_type, entity):
770 if sub_type == 'subscribed':
771 dialog.InfoDialog('Subscription confirmation', 'The contact <b>%s</b> has added you to his/her contact list' % html_sanitize(entity)).show()
772 self.getEntityMBlog(entity)
773
774 elif sub_type == 'unsubscribed':
775 dialog.InfoDialog('Subscription refusal', 'The contact <b>%s</b> has refused to add you in his/her contact list' % html_sanitize(entity)).show()
776 #TODO: remove microblogs from panels
777
778 elif sub_type == 'subscribe':
779 #The user want to subscribe to our presence
780 _dialog = None
781 msg = HTML('The contact <b>%s</b> want to add you in his/her contact list, do you accept ?' % html_sanitize(entity))
782
783 def ok_cb(ignore):
784 self.bridge.call('subscription', None, "subscribed", entity, '', _dialog.getSelectedGroups())
785 def cancel_cb(ignore):
786 self.bridge.call('subscription', None, "unsubscribed", entity, '', '')
787
788 _dialog = dialog.GroupSelector([msg], self.contact_panel.getGroups(), [], "Add", ok_cb, cancel_cb)
789 _dialog.setHTML('<b>Add contact request</b>')
790 _dialog.show()
791
792 def _contactDeletedCb(self, entity):
793 self.contact_panel.removeContact(entity)
794
795 def _newContactCb(self, contact, attributes, groups):
796 self.contact_panel.updateContact(contact, attributes, groups)
797
798 def _entityDataUpdatedCb(self, entity_jid_s, key, value):
799 if key == "avatar":
800 avatar = '/avatars/%s' % value
801
802 self.avatars_cache[entity_jid_s] = avatar
803
804 for lib_wid in self.libervia_widgets:
805 if isinstance(lib_wid, panels.MicroblogPanel):
806 if lib_wid.isJidAccepted(entity_jid_s) or (self.whoami and entity_jid_s == self.whoami.bare):
807 lib_wid.updateValue('avatar', entity_jid_s, avatar)
808
809 def _chatStateReceivedCb(self, from_jid_s, state):
810 """Callback when a new chat state is received.
811 @param from_jid_s: JID of the contact who sent his state, or '@ALL@'
812 @param state: new state (string)
813 """
814 if from_jid_s == '@ALL@':
815 target = '@ALL@'
816 nick = C.ALL_OCCUPANTS
817 else:
818 from_jid = JID(from_jid_s)
819 target = from_jid.bare
820 nick = from_jid.resource
821
822 for lib_wid in self.libervia_widgets:
823 if isinstance(lib_wid, panels.ChatPanel):
824 if target == '@ALL' or target == lib_wid.target.bare:
825 if lib_wid.type == 'one2one':
826 lib_wid.setState(state)
827 elif lib_wid.type == 'group':
828 lib_wid.setState(state, nick=nick)
829
830 def _askConfirmation(self, confirmation_id, confirmation_type, data):
831 answer_data = {}
832
833 def confirm_cb(result):
834 self.bridge.call('confirmationAnswer', None, confirmation_id, result, answer_data)
835
836 if confirmation_type == "YES/NO":
837 dialog.ConfirmDialog(confirm_cb, text=data["message"], title=data["title"]).show()
838
839 def _newAlert(self, message, title, alert_type):
840 dialog.InfoDialog(title, message).show()
841
842 def _paramUpdate(self, name, value, category, refresh=True):
843 """This is called when the paramUpdate signal is received, but also
844 during initialization when the UI parameters values are retrieved.
845 @param refresh: set to True to refresh the general UI
846 """
847 for param in self.params_ui:
848 if name == self.params_ui[param]['name']:
849 self.params_ui[param]['value'] = self.params_ui[param]['cast'](value)
850 if refresh:
851 self.refresh()
852 break
853
854 def sendError(self, errorData):
855 dialog.InfoDialog("Error while sending message",
856 "Your message can't be sent", Width="400px").center()
857 log.error("sendError: %s" % str(errorData))
858
859 def send(self, targets, text, extra={}):
860 """Send a message to any target type.
861 @param targets: list of tuples (type, entities, addr) with:
862 - type in ("PUBLIC", "GROUP", "COMMENT", "STATUS" , "groupchat" , "chat")
863 - entities could be a JID, a list groups, a node hash... depending the target
864 - addr in ("To", "Cc", "Bcc") - ignore case
865 @param text: the message content
866 @param extra: options
867 """
868 # FIXME: too many magic strings, we should use constants instead
869 addresses = []
870 for target in targets:
871 type_, entities, addr = target[0], target[1], 'to' if len(target) < 3 else target[2].lower()
872 if type_ in ("PUBLIC", "GROUP"):
873 self.bridge.call("sendMblog", None, type_, entities if type_ == "GROUP" else None, text, extra)
874 elif type_ == "COMMENT":
875 self.bridge.call("sendMblogComment", None, entities, text, extra)
876 elif type_ == "STATUS":
877 self.bridge.call('setStatus', None, self.status_panel.presence, text)
878 elif type_ in ("groupchat", "chat"):
879 addresses.append((addr, entities))
880 else:
881 log.error("Unknown target type")
882 if addresses:
883 if len(addresses) == 1 and addresses[0][0] == 'to':
884 self.bridge.call('sendMessage', (None, self.sendError), addresses[0][1], text, '', type_, extra)
885 else:
886 extra.update({'address': '\n'.join([('%s:%s' % entry) for entry in addresses])})
887 self.bridge.call('sendMessage', (None, self.sendError), self.whoami.domain, text, '', type_, extra)
888
889 def showWarning(self, type_=None, msg=None):
890 """Display a popup information message, e.g. to notify the recipient of a message being composed.
891 If type_ is None, a popup being currently displayed will be hidden.
892 @type_: a type determining the CSS style to be applied (see WarningPopup.showWarning)
893 @msg: message to be displayed
894 """
895 if not hasattr(self, "warning_popup"):
896 self.warning_popup = panels.WarningPopup()
897 self.warning_popup.showWarning(type_, msg)
898
899
900 if __name__ == '__main__':
901 app = SatWebFrontend()
902 app.onModuleLoad()
903 pyjd.run()