Mercurial > libervia-backend
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())) |