Mercurial > libervia-backend
view frontends/src/quick_frontend/quick_app.py @ 985:9ebdba4ab907
plugin XEP-0048: bookmarks deletion + added "delete" option for /bookmark command
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 07 Apr 2014 16:24:29 +0200 |
parents | cad958239b5c |
children | d1084f7e56a5 |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- # helper class for making a SAT frontend # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.i18n import _ import sys from logging import debug, info, warning, error from sat.tools.jid import JID from sat_frontends.bridge.DBus import DBusBridgeFrontend from sat.core.exceptions import BridgeExceptionNoService, BridgeInitError from sat_frontends.quick_frontend.quick_utils import escapePrivate, unescapePrivate from optparse import OptionParser from sat_frontends.quick_frontend.constants import Const class QuickApp(object): """This class contain the main methods needed for the frontend""" def __init__(self, single_profile=True): self.profiles = {} self.single_profile = single_profile self.check_options() ## bridge ## try: self.bridge = DBusBridgeFrontend() except BridgeExceptionNoService: print(_(u"Can't connect to SàT backend, are you sure it's launched ?")) sys.exit(1) except BridgeInitError: print(_(u"Can't init bridge")) sys.exit(1) self.bridge.register("connected", self.connected) self.bridge.register("disconnected", self.disconnected) self.bridge.register("connectionError", self.connectionError) self.bridge.register("newContact", self.newContact) self.bridge.register("newMessage", self._newMessage) self.bridge.register("newAlert", self.newAlert) self.bridge.register("presenceUpdate", self.presenceUpdate) self.bridge.register("subscribe", self.subscribe) self.bridge.register("paramUpdate", self.paramUpdate) self.bridge.register("contactDeleted", self.contactDeleted) self.bridge.register("entityDataUpdated", self.entityDataUpdated) self.bridge.register("askConfirmation", self.askConfirmation) self.bridge.register("actionResult", self.actionResult) self.bridge.register("actionResultExt", self.actionResult) self.bridge.register("roomJoined", self.roomJoined, "plugin") self.bridge.register("roomLeft", self.roomLeft, "plugin") self.bridge.register("roomUserJoined", self.roomUserJoined, "plugin") self.bridge.register("roomUserLeft", self.roomUserLeft, "plugin") self.bridge.register("roomUserChangedNick", self.roomUserChangedNick, "plugin") self.bridge.register("roomNewSubject", self.roomNewSubject, "plugin") self.bridge.register("tarotGameStarted", self.tarotGameStarted, "plugin") self.bridge.register("tarotGameNew", self.tarotGameNew, "plugin") self.bridge.register("tarotGameChooseContrat", self.tarotChooseContrat, "plugin") self.bridge.register("tarotGameShowCards", self.tarotShowCards, "plugin") self.bridge.register("tarotGameYourTurn", self.tarotMyTurn, "plugin") self.bridge.register("tarotGameScore", self.tarotScore, "plugin") self.bridge.register("tarotGameCardsPlayed", self.tarotCardsPlayed, "plugin") self.bridge.register("tarotGameInvalidCards", self.tarotInvalidCards, "plugin") self.bridge.register("quizGameStarted", self.quizGameStarted, "plugin") self.bridge.register("quizGameNew", self.quizGameNew, "plugin") self.bridge.register("quizGameQuestion", self.quizGameQuestion, "plugin") self.bridge.register("quizGamePlayerBuzzed", self.quizGamePlayerBuzzed, "plugin") self.bridge.register("quizGamePlayerSays", self.quizGamePlayerSays, "plugin") self.bridge.register("quizGameAnswerResult", self.quizGameAnswerResult, "plugin") self.bridge.register("quizGameTimerExpired", self.quizGameTimerExpired, "plugin") self.bridge.register("quizGameTimerRestarted", self.quizGameTimerRestarted, "plugin") self.bridge.register("chatStateReceived", self.chatStateReceived, "plugin") self.current_action_ids = set() self.current_action_ids_cb = {} self.media_dir = self.bridge.getConfig('', 'media_dir') def check_profile(self, profile): """Tell if the profile is currently followed by the application""" return profile in self.profiles.keys() def postInit(self): """Must be called after initialization is done, do all automatic task (auto plug profile)""" if self.options.profile: if not self.bridge.getProfileName(self.options.profile): error(_("Trying to plug an unknown profile (%s)" % self.options.profile)) else: self.plug_profile(self.options.profile) def check_options(self): """Check command line options""" usage = _(""" %prog [options] %prog --help for options list """) parser = OptionParser(usage=usage) parser.add_option("-p", "--profile", help=_("Select the profile to use")) (self.options, args) = parser.parse_args() if self.options.profile: self.options.profile = self.options.profile.decode('utf-8') return args def _getParamError(self, ignore): error(_("Can't get profile parameter")) def plug_profile(self, profile_key='@DEFAULT@'): """Tell application which profile must be used""" if self.single_profile and self.profiles: error(_('There is already one profile plugged (we are in single profile mode) !')) return profile = self.bridge.getProfileName(profile_key) if not profile: error(_("The profile asked doesn't exist")) return if profile in self.profiles: warning(_("The profile is already plugged")) return self.profiles[profile] = {} if self.single_profile: self.profile = profile ###now we get the essential params### self.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile, callback=lambda _jid: self.plug_profile_2(_jid, profile), errback=self._getParamError) def plug_profile_2(self, _jid, profile): self.profiles[profile]['whoami'] = JID(_jid) self.bridge.asyncGetParamA("autoconnect", "Connection", profile_key=profile, callback=lambda value: self.plug_profile_3(value == "true", profile), errback=self._getParamError) def plug_profile_3(self, autoconnect, profile): self.bridge.asyncGetParamA("Watched", "Misc", profile_key=profile, callback=lambda watched: self.plug_profile_4(watched, autoconnect, profile), errback=self._getParamError) def plug_profile_4(self, watched, autoconnect, profile): if autoconnect and not self.bridge.isConnected(profile): #Does the user want autoconnection ? self.bridge.asyncConnect(profile, callback=lambda: self.plug_profile_5(watched, autoconnect, profile), errback=lambda ignore: error(_('Error during autoconnection'))) else: self.plug_profile_5(watched, autoconnect, profile) def plug_profile_5(self, watched, autoconnect, profile): self.profiles[profile]['watched'] = watched.split() # TODO: put this in a plugin ## misc ## self.profiles[profile]['onlineContact'] = set() # FIXME: temporary #TODO: manage multi-profiles here if not self.bridge.isConnected(profile): self.setStatusOnline(False) else: self.setStatusOnline(True) ### now we fill the contact list ### for contact in self.bridge.getContacts(profile): self.newContact(*contact, profile=profile) presences = self.bridge.getPresenceStatuses(profile) for contact in presences: for res in presences[contact]: jabber_id = contact + ('/' + res if res else '') show = presences[contact][res][0] priority = presences[contact][res][1] statuses = presences[contact][res][2] self.presenceUpdate(jabber_id, show, priority, statuses, profile) data = self.bridge.getEntityData(contact, ['avatar', 'nick'], profile) for key in ('avatar', 'nick'): if key in data: self.entityDataUpdated(contact, key, data[key], profile) #The waiting subscription requests waitingSub = self.bridge.getWaitingSub(profile) for sub in waitingSub: self.subscribe(waitingSub[sub], sub, profile) #Now we open the MUC window where we already are: for room_args in self.bridge.getRoomsJoined(profile): self.roomJoined(*room_args, profile=profile) for subject_args in self.bridge.getRoomsSubjects(profile): self.roomNewSubject(*subject_args, profile=profile) #Finaly, we get the waiting confirmation requests for confirm_id, confirm_type, data in self.bridge.getWaitingConf(profile): self.askConfirmation(confirm_id, confirm_type, data, profile) def unplug_profile(self, profile): """Tell the application to not follow anymore the profile""" if not profile in self.profiles: warning(_("This profile is not plugged")) return self.profiles.remove(profile) def clear_profile(self): self.profiles.clear() def connected(self, profile): """called when the connection is made""" if not self.check_profile(profile): return debug(_("Connected")) self.setStatusOnline(True) def disconnected(self, profile): """called when the connection is closed""" if not self.check_profile(profile): return debug(_("Disconnected")) self.contact_list.clearContacts() self.setStatusOnline(False) def connectionError(self, error_type, profile): """called when something goes wrong with the connection""" if not self.check_profile(profile): return debug(_("Connection Error")) self.disconnected(profile) if error_type == "AUTH_ERROR": self.showDialog(_("Can't connect to account, please check your password"), _("Connection error"), "error") else: error(_('FIXME: error_type %s not implemented') % error_type) def newContact(self, JabberId, attributes, groups, profile): if not self.check_profile(profile): return entity = JID(JabberId) _groups = list(groups) self.contact_list.replace(entity, _groups, attributes) def _newMessage(self, from_jid_s, msg, _type, to_jid_s, extra, profile): """newMessage premanagement: a dirty hack to manage private messages if a private MUC message is detected, from_jid or to_jid is prefixed and resource is escaped""" if not self.check_profile(profile): return from_jid = JID(from_jid_s) to_jid = JID(to_jid_s) from_me = from_jid.bare == self.profiles[profile]['whoami'].bare win = to_jid if from_me else from_jid if _type != "groupchat" and self.contact_list.getSpecial(win) == "MUC": #we have a private message in a MUC room #XXX: normaly we use bare jid as key, here we need the full jid # so we cheat by replacing the "/" before the resource by # a "@", so the jid is invalid, new_jid = escapePrivate(win) if from_me: to_jid = new_jid else: from_jid = new_jid if new_jid not in self.contact_list: self.contact_list.add(new_jid) self.newMessage(from_jid, to_jid, msg, _type, extra, profile) def newMessage(self, from_jid, to_jid, msg, _type, extra, profile): from_me = from_jid.bare == self.profiles[profile]['whoami'].bare win = to_jid if from_me else from_jid self.current_action_ids = set() self.current_action_ids_cb = {} timestamp = extra.get('archive') self.chat_wins[win.bare].printMessage(from_jid, msg, profile, float(timestamp) if timestamp else '') def sendMessage(self, to_jid, message, subject='', mess_type="auto", extra={}, callback=None, errback=None, profile_key="@NONE@"): if to_jid.startswith(Const.PRIVATE_PREFIX): to_jid = unescapePrivate(to_jid) mess_type = "chat" if callback is None: callback = lambda: None if errback is None: errback = lambda failure: self.showDialog(unicode(failure), _(u"sendMessage Error"), "error") self.bridge.sendMessage(to_jid, message, subject, mess_type, extra, profile_key, callback=callback, errback=errback) def newAlert(self, msg, title, alert_type, profile): if not self.check_profile(profile): return assert alert_type in ['INFO', 'ERROR'] self.showDialog(unicode(msg), unicode(title), alert_type.lower()) def setStatusOnline(self, online=True, show="", statuses={}): raise NotImplementedError def presenceUpdate(self, jabber_id, show, priority, statuses, profile): if not self.check_profile(profile): return debug(_("presence update for %(jid)s (show=%(show)s, priority=%(priority)s, statuses=%(statuses)s) [profile:%(profile)s]") % {'jid': jabber_id, 'show': show, 'priority': priority, 'statuses': statuses, 'profile': profile}) from_jid = JID(jabber_id) if from_jid == self.profiles[profile]['whoami']: if show == "unavailable": self.setStatusOnline(False) else: self.setStatusOnline(True, show, statuses) return presences = self.profiles[profile].setdefault('presences', {}) if show != 'unavailable': #FIXME: must be moved in a plugin if from_jid.bare in self.profiles[profile].get('watched',[]) and not from_jid.bare in self.profiles[profile]['onlineContact']: self.showAlert(_("Watched jid [%s] is connected !") % from_jid.bare) presences[jabber_id] = {'show': show, 'priority': priority, 'statuses': statuses} self.profiles[profile].setdefault('onlineContact',set()).add(from_jid) # FIXME onlineContact is useless with CM, must be removed #TODO: vcard data (avatar) if show == "unavailable" and from_jid in self.profiles[profile].get('onlineContact',set()): try: del presences[jabber_id] except KeyError: pass self.profiles[profile]['onlineContact'].remove(from_jid) # check if the contact is connected with another resource, use the one with highest priority jids = [jid for jid in presences if JID(jid).bare == from_jid.bare] if jids: max_jid = max(jids, key=lambda jid: presences[jid]['priority']) data = presences[max_jid] max_priority = data['priority'] if show == "unavailable": # do not check the priority here, because 'unavailable' has a dummy one from_jid = JID(max_jid) show, priority, statuses = data['show'], data['priority'], data['statuses'] if not jids or priority >= max_priority: # case 1: not jids means all resources are disconnected, send the 'unavailable' presence # case 2: update (or confirm) with the values of the resource which takes precedence self.contact_list.updatePresence(from_jid, show, priority, statuses) def roomJoined(self, room_jid, room_nicks, user_nick, profile): """Called when a MUC room is joined""" if not self.check_profile(profile): return debug(_("Room [%(room_jid)s] joined by %(profile)s, users presents:%(users)s") % {'room_jid': room_jid, 'profile': profile, 'users': room_nicks}) self.chat_wins[room_jid].setUserNick(user_nick) self.chat_wins[room_jid].setType("group") self.chat_wins[room_jid].id = room_jid self.chat_wins[room_jid].setPresents(list(set([user_nick] + room_nicks))) self.contact_list.setSpecial(JID(room_jid), "MUC", show=True) def roomLeft(self, room_jid_s, profile): """Called when a MUC room is left""" if not self.check_profile(profile): return debug(_("Room [%(room_jid)s] left by %(profile)s") % {'room_jid': room_jid_s, 'profile': profile}) del self.chat_wins[room_jid_s] self.contact_list.remove(JID(room_jid_s)) def roomUserJoined(self, room_jid, user_nick, user_data, profile): """Called when an user joined a MUC room""" if not self.check_profile(profile): return if room_jid in self.chat_wins: self.chat_wins[room_jid].replaceUser(user_nick) debug(_("user [%(user_nick)s] joined room [%(room_jid)s]") % {'user_nick': user_nick, 'room_jid': room_jid}) def roomUserLeft(self, room_jid, user_nick, user_data, profile): """Called when an user joined a MUC room""" if not self.check_profile(profile): return if room_jid in self.chat_wins: self.chat_wins[room_jid].removeUser(user_nick) debug(_("user [%(user_nick)s] left room [%(room_jid)s]") % {'user_nick': user_nick, 'room_jid': room_jid}) def roomUserChangedNick(self, room_jid, old_nick, new_nick, profile): """Called when an user joined a MUC room""" if not self.check_profile(profile): return if room_jid in self.chat_wins: self.chat_wins[room_jid].changeUserNick(old_nick, new_nick) debug(_("user [%(old_nick)s] is now known as [%(new_nick)s] in room [%(room_jid)s]") % {'old_nick': old_nick, 'new_nick': new_nick, 'room_jid': room_jid}) def roomNewSubject(self, room_jid, subject, profile): """Called when subject of MUC room change""" if not self.check_profile(profile): return if room_jid in self.chat_wins: self.chat_wins[room_jid].setSubject(subject) debug(_("new subject for room [%(room_jid)s]: %(subject)s") % {'room_jid': room_jid, "subject": subject}) def tarotGameStarted(self, room_jid, referee, players, profile): if not self.check_profile(profile): return debug(_("Tarot Game Started \o/")) if room_jid in self.chat_wins: self.chat_wins[room_jid].startGame("Tarot", referee, players) debug(_("new Tarot game started by [%(referee)s] in room [%(room_jid)s] with %(players)s") % {'referee': referee, 'room_jid': room_jid, 'players': [str(player) for player in players]}) def tarotGameNew(self, room_jid, hand, profile): if not self.check_profile(profile): return debug(_("New Tarot Game")) if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Tarot").newGame(hand) def tarotChooseContrat(self, room_jid, xml_data, profile): """Called when the player has to select his contrat""" if not self.check_profile(profile): return debug(_("Tarot: need to select a contrat")) if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Tarot").chooseContrat(xml_data) def tarotShowCards(self, room_jid, game_stage, cards, data, profile): if not self.check_profile(profile): return debug(_("Show cards")) if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Tarot").showCards(game_stage, cards, data) def tarotMyTurn(self, room_jid, profile): if not self.check_profile(profile): return debug(_("My turn to play")) if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Tarot").myTurn() def tarotScore(self, room_jid, xml_data, winners, loosers, profile): """Called when the game is finished and the score are updated""" if not self.check_profile(profile): return debug(_("Tarot: score received")) if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Tarot").showScores(xml_data, winners, loosers) def tarotCardsPlayed(self, room_jid, player, cards, profile): if not self.check_profile(profile): return debug(_("Card(s) played (%(player)s): %(cards)s") % {"player": player, "cards": cards}) if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Tarot").cardsPlayed(player, cards) def tarotInvalidCards(self, room_jid, phase, played_cards, invalid_cards, profile): if not self.check_profile(profile): return debug(_("Cards played are not valid: %s") % invalid_cards) if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Tarot").invalidCards(phase, played_cards, invalid_cards) def quizGameStarted(self, room_jid, referee, players, profile): if not self.check_profile(profile): return debug(_("Quiz Game Started \o/")) if room_jid in self.chat_wins: self.chat_wins[room_jid].startGame("Quiz", referee, players) debug(_("new Quiz game started by [%(referee)s] in room [%(room_jid)s] with %(players)s") % {'referee': referee, 'room_jid': room_jid, 'players': [str(player) for player in players]}) def quizGameNew(self, room_jid, data, profile): if not self.check_profile(profile): return debug(_("New Quiz Game")) if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Quiz").quizGameNew(data) def quizGameQuestion(self, room_jid, question_id, question, timer, profile): """Called when a new question is asked""" if not self.check_profile(profile): return debug(_(u"Quiz: new question: %s") % question) if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Quiz").quizGameQuestion(question_id, question, timer) def quizGamePlayerBuzzed(self, room_jid, player, pause, profile): """Called when a player pushed the buzzer""" if not self.check_profile(profile): return if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Quiz").quizGamePlayerBuzzed(player, pause) def quizGamePlayerSays(self, room_jid, player, text, delay, profile): """Called when a player say something""" if not self.check_profile(profile): return if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Quiz").quizGamePlayerSays(player, text, delay) def quizGameAnswerResult(self, room_jid, player, good_answer, score, profile): """Called when a player say something""" if not self.check_profile(profile): return if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Quiz").quizGameAnswerResult(player, good_answer, score) def quizGameTimerExpired(self, room_jid, profile): """Called when nobody answered the question in time""" if not self.check_profile(profile): return if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Quiz").quizGameTimerExpired() def quizGameTimerRestarted(self, room_jid, time_left, profile): """Called when the question is not answered, and we still have time""" if not self.check_profile(profile): return if room_jid in self.chat_wins: self.chat_wins[room_jid].getGame("Quiz").quizGameTimerRestarted(time_left) def chatStateReceived(self, from_jid_s, state, profile): """Callback when a new chat state is received. @param from_jid_s: JID of the contact who sent his state, or '@ALL@' @param state: new state (string) @profile: current profile """ if not self.check_profile(profile): return if from_jid_s == '@ALL@': target = '@ALL@' nick = Const.ALL_OCCUPANTS else: from_jid = JID(from_jid_s) target = from_jid.bare nick = from_jid.resource for bare in self.chat_wins.keys(): if target == '@ALL' or target == bare: chat_win = self.chat_wins[bare] if chat_win.type == 'one2one': chat_win.updateChatState(state) elif chat_win.type == 'group': chat_win.updateChatState(state, nick=nick) def _subscribe_cb(self, answer, data): entity, profile = data if answer: self.bridge.subscription("subscribed", entity.bare, profile_key=profile) else: self.bridge.subscription("unsubscribed", entity.bare, profile_key=profile) def subscribe(self, type, raw_jid, profile): """Called when a subsciption management signal is received""" if not self.check_profile(profile): return entity = JID(raw_jid) if type == "subscribed": # this is a subscription confirmation, we just have to inform user self.showDialog(_("The contact %s has accepted your subscription") % entity.bare, _('Subscription confirmation')) elif type == "unsubscribed": # this is a subscription refusal, we just have to inform user self.showDialog(_("The contact %s has refused your subscription") % entity.bare, _('Subscription refusal'), 'error') elif type == "subscribe": # this is a subscriptionn request, we have to ask for user confirmation self.showDialog(_("The contact %s wants to subscribe to your presence.\nDo you accept ?") % entity.bare, _('Subscription confirmation'), 'yes/no', answer_cb=self._subscribe_cb, answer_data=(entity, profile)) def showDialog(self, message, title, type="info", answer_cb=None): raise NotImplementedError def showAlert(self, message): pass #FIXME def paramUpdate(self, name, value, namespace, profile): if not self.check_profile(profile): return debug(_("param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace': namespace, 'name': name, 'value': value}) if (namespace, name) == ("Connection", "JabberID"): debug(_("Changing JID to %s"), value) self.profiles[profile]['whoami'] = JID(value) elif (namespace, name) == ("Misc", "Watched"): self.profiles[profile]['watched'] = value.split() def contactDeleted(self, jid, profile): if not self.check_profile(profile): return target = JID(jid) self.contact_list.remove(target) try: self.profiles[profile]['onlineContact'].remove(target.bare) except KeyError: pass def entityDataUpdated(self, jid_str, key, value, profile): if not self.check_profile(profile): return jid = JID(jid_str) if key == "nick": if jid in self.contact_list: self.contact_list.setCache(jid, 'nick', value) self.contact_list.replace(jid) elif key == "avatar": if jid in self.contact_list: filename = self.bridge.getAvatarFile(value) self.contact_list.setCache(jid, 'avatar', filename) self.contact_list.replace(jid) def askConfirmation(self, confirm_id, confirm_type, data, profile): raise NotImplementedError def actionResult(self, type, id, data): raise NotImplementedError def onExit(self): """Must be called when the frontend is terminating""" #TODO: mange multi-profile here try: if self.bridge.isConnected(self.profile): if self.bridge.getParamA("autodisconnect", "Connection", profile_key=self.profile) == "true": #The user wants autodisconnection self.bridge.disconnect(self.profile) except: pass