comparison src/plugins/plugin_xep_0085.py @ 1290:faa1129559b8 frontends_multi_profiles

core, frontends: refactoring to base Libervia on QuickFrontend (big mixed commit): /!\ not finished, everything is still instable ! - bridge: DBus bridge has been modified to allow blocking call to be called in the same way as asynchronous calls - bridge: calls with a callback and no errback are now possible, default errback log the error - constants: removed hack to manage presence without OrderedDict, as an OrderedDict like class has been implemented in Libervia - core: getLastResource has been removed and replaced by getMainResource (there is a global better management of resources) - various style improvments: use of constants when possible, fixed variable overlaps, import of module instead of direct class import - frontends: printInfo and printMessage methods in (Quick)Chat are more generic (use of extra instead of timestamp) - frontends: bridge creation and option parsing (command line arguments) are now specified by the frontend in QuickApp __init__ - frontends: ProfileManager manage a more complete plug sequence (some stuff formerly manage in contact_list have moved to ProfileManager) - quick_frontend (quick_widgets): QuickWidgetsManager is now iterable (all widgets are then returned), or can return an iterator on a specific class (return all widgets of this class) with getWidgets - frontends: tools.jid can now be used in Pyjamas, with some care - frontends (XMLUI): profile is now managed - core (memory): big improvment on entities cache management (and specially resource management) - core (params/exceptions): added PermissionError - various fixes and improvments, check diff for more details
author Goffi <goffi@goffi.org>
date Sat, 24 Jan 2015 01:00:29 +0100
parents 60dfa2f5d61f
children be3a301540c0
comparison
equal deleted inserted replaced
1289:653f2e2eea31 1290:faa1129559b8
37 CHAT_STATES = ["active", "inactive", "gone", "composing", "paused"] 37 CHAT_STATES = ["active", "inactive", "gone", "composing", "paused"]
38 MESSAGE_TYPES = ["chat", "groupchat"] 38 MESSAGE_TYPES = ["chat", "groupchat"]
39 PARAM_KEY = "Notifications" 39 PARAM_KEY = "Notifications"
40 PARAM_NAME = "Enable chat state notifications" 40 PARAM_NAME = "Enable chat state notifications"
41 ENTITY_KEY = PARAM_KEY + "_" + PARAM_NAME 41 ENTITY_KEY = PARAM_KEY + "_" + PARAM_NAME
42 DELETE_VALUE = "DELETE"
42 43
43 PLUGIN_INFO = { 44 PLUGIN_INFO = {
44 "name": "Chat State Notifications Protocol Plugin", 45 "name": "Chat State Notifications Protocol Plugin",
45 "import_name": "XEP-0085", 46 "import_name": "XEP-0085",
46 "type": "XEP", 47 "type": "XEP",
124 # actually defer the disconnection 125 # actually defer the disconnection
125 self.map[profile][to_jid]._onEvent('gone') 126 self.map[profile][to_jid]._onEvent('gone')
126 del self.map[profile] 127 del self.map[profile]
127 128
128 def updateEntityData(self, entity_jid, value, profile): 129 def updateEntityData(self, entity_jid, value, profile):
129 """ 130 """Update the entity data of the given profile for one or all contacts.
130 Update the entity data of the given profile for one or all contacts. 131
131 Reset the chat state(s) display if the notification has been disabled. 132 Reset the chat state(s) display if the notification has been disabled.
132 @param entity_jid: contact's JID, or '@ALL@' to update all contacts. 133 @param entity_jid: contact's JID, or C.ENTITY_ALL to update all contacts.
133 @param value: True, False or C.PROF_KEY_NONE to delete the entity data 134 @param value: True, False or DELETE_VALUE to delete the entity data
134 @param profile: current profile 135 @param profile: current profile
135 """ 136 """
136 self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile) 137 if value == DELETE_VALUE:
137 if not value or value == C.PROF_KEY_NONE: 138 self.host.memory.delEntityData(entity_jid, ENTITY_KEY, profile)
138 # disable chat state for this or these contact(s) 139 else:
139 self.host.bridge.chatStateReceived(unicode(entity_jid), "", profile) 140 self.host.memory.updateEntityData(entity_jid, ENTITY_KEY, value, profile)
141 if not value or value == DELETE_VALUE:
142 # reinit chat state UI for this or these contact(s)
143 self.host.bridge.chatStateReceived(entity_jid.full(), "", profile)
140 144
141 def paramUpdateTrigger(self, name, value, category, type_, profile): 145 def paramUpdateTrigger(self, name, value, category, type_, profile):
142 """ 146 """Reset all the existing chat state entity data associated with this profile after a parameter modification.
143 Reset all the existing chat state entity data associated with this profile after a parameter modification. 147
144 @param name: parameter name 148 @param name: parameter name
145 @param value: "true" to activate the notifications, or any other value to delete it 149 @param value: "true" to activate the notifications, or any other value to delete it
146 @param category: parameter category 150 @param category: parameter category
147 @param type_: parameter type 151 @param type_: parameter type
148 """ 152 """
149 if (category, name) == (PARAM_KEY, PARAM_NAME): 153 if (category, name) == (PARAM_KEY, PARAM_NAME):
150 self.updateEntityData("@ALL@", True if value == "true" else C.PROF_KEY_NONE, profile) 154 self.updateEntityData(C.ENTITY_ALL, True if bool("true") else DELETE_VALUE, profile)
151 return False 155 return False
152 return True 156 return True
153 157
154 def messageReceivedTrigger(self, message, post_treat, profile): 158 def messageReceivedTrigger(self, message, post_treat, profile):
155 """ 159 """
158 """ 162 """
159 if not self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile): 163 if not self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile):
160 return True 164 return True
161 165
162 from_jid = JID(message.getAttribute("from")) 166 from_jid = JID(message.getAttribute("from"))
163 if self.__isMUC(from_jid, profile): 167 if self._isMUC(from_jid, profile):
164 from_jid = from_jid.userhostJID() 168 from_jid = from_jid.userhostJID()
165 else: # update entity data for one2one chat 169 else: # update entity data for one2one chat
166 # assert from_jid.resource # FIXME: assert doesn't work on normal message from server (e.g. server announce), because there is no resource 170 # assert from_jid.resource # FIXME: assert doesn't work on normal message from server (e.g. server announce), because there is no resource
167 try: 171 try:
168 domish.generateElementsNamed(message.elements(), name="body").next() 172 domish.generateElementsNamed(message.elements(), name="body").next()
198 the state machine when sending an "active" state. 202 the state machine when sending an "active" state.
199 """ 203 """
200 def treatment(mess_data): 204 def treatment(mess_data):
201 message = mess_data['xml'] 205 message = mess_data['xml']
202 to_jid = JID(message.getAttribute("to")) 206 to_jid = JID(message.getAttribute("to"))
203 if not self.__checkActivation(to_jid, forceEntityData=True, profile=profile): 207 if not self._checkActivation(to_jid, forceEntityData=True, profile=profile):
204 return mess_data 208 return mess_data
205 try: 209 try:
206 # message with a body always mean active state 210 # message with a body always mean active state
207 domish.generateElementsNamed(message.elements(), name="body").next() 211 domish.generateElementsNamed(message.elements(), name="body").next()
208 message.addElement('active', NS_CHAT_STATES) 212 message.addElement('active', NS_CHAT_STATES)
209 # launch the chat state machine (init the timer) 213 # launch the chat state machine (init the timer)
210 if self.__isMUC(to_jid, profile): 214 if self._isMUC(to_jid, profile):
211 to_jid = to_jid.userhostJID() 215 to_jid = to_jid.userhostJID()
212 self.__chatStateActive(to_jid, mess_data["type"], profile) 216 self.__chatStateActive(to_jid, mess_data["type"], profile)
213 except StopIteration: 217 except StopIteration:
214 if "chat_state" in mess_data["extra"]: 218 if "chat_state" in mess_data["extra"]:
215 state = mess_data["extra"].pop("chat_state") 219 state = mess_data["extra"].pop("chat_state")
216 assert(state in CHAT_STATES) 220 assert state in CHAT_STATES
217 message.addElement(state, NS_CHAT_STATES) 221 message.addElement(state, NS_CHAT_STATES)
218 return mess_data 222 return mess_data
219 223
220 post_xml_treatments.addCallback(treatment) 224 post_xml_treatments.addCallback(treatment)
221 return True 225 return True
222 226
223 def __isMUC(self, to_jid, profile): 227 def _isMUC(self, to_jid, profile):
224 """Tell if that JID is a MUC or not 228 """Tell if that JID is a MUC or not
225 229
226 @param to_jid (JID): full or bare JID to check 230 @param to_jid (JID): full or bare JID to check
227 @param profile (str): %(doc_profile)s 231 @param profile (str): %(doc_profile)s
228 @return: bool 232 @return: bool
229 """ 233 """
230 try: 234 try:
231 type_ = self.host.memory.getEntityDatum(to_jid.userhostJID(), 'type', profile) 235 type_ = self.host.memory.getEntityDatum(to_jid.userhostJID(), 'type', profile)
232 if type_ == 'chatroom': 236 if type_ == 'chatroom': # FIXME: should not use disco instead ?
233 return True 237 return True
234 except (exceptions.UnknownEntityError, KeyError): 238 except (exceptions.UnknownEntityError, KeyError):
235 pass 239 pass
236 return False 240 return False
237 241
238 def __checkActivation(self, to_jid, forceEntityData, profile): 242 def _checkActivation(self, to_jid, forceEntityData, profile):
239 """ 243 """
240 @param to_jid: the contact's full JID (or bare if you know it's a MUC) 244 @param to_jid: the contact's full JID (or bare if you know it's a MUC)
241 @param forceEntityData: if set to True, a non-existing 245 @param forceEntityData: if set to True, a non-existing
242 entity data will be considered to be True (and initialized) 246 entity data will be considered to be True (and initialized)
243 @param: current profile 247 @param: current profile
245 """ 249 """
246 # check if the parameter is active 250 # check if the parameter is active
247 if not self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile): 251 if not self.host.memory.getParamA(PARAM_NAME, PARAM_KEY, profile_key=profile):
248 return False 252 return False
249 # check if notifications should be sent to this contact 253 # check if notifications should be sent to this contact
250 if self.__isMUC(to_jid, profile): 254 if self._isMUC(to_jid, profile):
251 return True 255 return True
252 assert to_jid.resource or not self.host.memory.isContactConnected(to_jid, profile) 256 # FIXME: this assertion crash when we want to send a message to an online bare jid
257 # assert to_jid.resource or not self.host.memory.isEntityAvailable(to_jid, profile) # must either have a resource, or talk to an offline contact
253 try: 258 try:
254 return self.host.memory.getEntityDatum(to_jid, ENTITY_KEY, profile) 259 return self.host.memory.getEntityDatum(to_jid, ENTITY_KEY, profile)
255 except (exceptions.UnknownEntityError, KeyError): 260 except (exceptions.UnknownEntityError, KeyError):
256 if forceEntityData: 261 if forceEntityData:
257 # enable it for the first time 262 # enable it for the first time
303 # TODO: try to optimize this method which is called often 308 # TODO: try to optimize this method which is called often
304 profile = self.host.memory.getProfileName(profile_key) 309 profile = self.host.memory.getProfileName(profile_key)
305 if profile is None: 310 if profile is None:
306 raise exceptions.ProfileUnknownError 311 raise exceptions.ProfileUnknownError
307 to_jid = JID(to_jid_s) 312 to_jid = JID(to_jid_s)
308 if self.__isMUC(to_jid, profile): 313 if self._isMUC(to_jid, profile):
309 to_jid = to_jid.userhostJID() 314 to_jid = to_jid.userhostJID()
310 elif not to_jid.resource: 315 elif not to_jid.resource:
311 to_jid.resource = self.host.memory.getLastResource(to_jid, profile) 316 to_jid.resource = self.host.memory.getMainResource(to_jid, profile)
312 if not self.__checkActivation(to_jid, forceEntityData=False, profile=profile): 317 if not self._checkActivation(to_jid, forceEntityData=False, profile=profile):
313 return 318 return
314 try: 319 try:
315 self.map[profile][to_jid]._onEvent("composing") 320 self.map[profile][to_jid]._onEvent("composing")
316 except (KeyError, AttributeError): 321 except (KeyError, AttributeError):
317 # no message has been sent/received since the notifications 322 # no message has been sent/received since the notifications
345 """ 350 """
346 Move to the specified state, eventually send the 351 Move to the specified state, eventually send the
347 notification to the contact (the "active" state is 352 notification to the contact (the "active" state is
348 automatically sent with each message) and set the timer. 353 automatically sent with each message) and set the timer.
349 """ 354 """
350 assert(state in TRANSITIONS) 355 assert state in TRANSITIONS
351 transition = TRANSITIONS[state] 356 transition = TRANSITIONS[state]
352 assert("next_state" in transition and "delay" in transition) 357 assert "next_state" in transition and "delay" in transition
353 358
354 if state != self.state and state != "active": 359 if state != self.state and state != "active":
355 if state != 'gone' or self.mess_type != 'groupchat': 360 if state != 'gone' or self.mess_type != 'groupchat':
356 # send a new message without body 361 # send a new message without body
357 log.debug(u"sending state '{state}' to {jid}".format(state=state, jid=self.to_jid.full())) 362 log.debug(u"sending state '{state}' to {jid}".format(state=state, jid=self.to_jid.full()))