comparison frontends/src/quick_frontend/quick_app.py @ 223:86d249b6d9b7

Files reorganisation
author Goffi <goffi@goffi.org>
date Wed, 29 Dec 2010 01:06:29 +0100
parents frontends/quick_frontend/quick_app.py@96186f36d8cb
children fd9b7834d98a
comparison
equal deleted inserted replaced
222:3198bfd66daa 223:86d249b6d9b7
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 """
5 helper class for making a SAT frontend
6 Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.org)
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """
21
22 from logging import debug, info, error
23 from tools.jid import JID
24 from sat_bridge_frontend.DBus import DBusBridgeFrontend,BridgeExceptionNoService
25 from optparse import OptionParser
26 import pdb
27
28 import gettext
29 gettext.install('sat_frontend', "../i18n", unicode=True)
30
31 class QuickApp():
32 """This class contain the main methods needed for the frontend"""
33
34 def __init__(self, single_profile=True):
35 self.rosterList = {}
36 self.profiles = {}
37 self.single_profile = single_profile
38 self.check_options()
39
40 ## bridge ##
41 try:
42 self.bridge=DBusBridgeFrontend()
43 except BridgeExceptionNoService:
44 print(_(u"Can't connect to SàT backend, are you sure it's launched ?"))
45 import sys
46 sys.exit(1)
47 self.bridge.register("connected", self.connected)
48 self.bridge.register("disconnected", self.disconnected)
49 self.bridge.register("newContact", self.newContact)
50 self.bridge.register("newMessage", self.newMessage)
51 self.bridge.register("newAlert", self.newAlert)
52 self.bridge.register("presenceUpdate", self.presenceUpdate)
53 self.bridge.register("roomJoined", self.roomJoined)
54 self.bridge.register("roomUserJoined", self.roomUserJoined)
55 self.bridge.register("roomUserLeft", self.roomUserLeft)
56 self.bridge.register("roomNewSubject", self.roomNewSubject)
57 self.bridge.register("tarotGameStarted", self.tarotGameStarted)
58 self.bridge.register("tarotGameNew", self.tarotGameNew)
59 self.bridge.register("tarotGameChooseContrat", self.tarotChooseContrat)
60 self.bridge.register("tarotGameShowCards", self.tarotShowCards)
61 self.bridge.register("tarotGameYourTurn", self.tarotMyTurn)
62 self.bridge.register("tarotGameScore", self.tarotScore)
63 self.bridge.register("tarotGameCardsPlayed", self.tarotCardsPlayed)
64 self.bridge.register("tarotGameInvalidCards", self.tarotInvalidCards)
65 self.bridge.register("subscribe", self.subscribe)
66 self.bridge.register("paramUpdate", self.paramUpdate)
67 self.bridge.register("contactDeleted", self.contactDeleted)
68 self.bridge.register("updatedValue", self.updatedValue, "request")
69 self.bridge.register("askConfirmation", self.askConfirmation, "request")
70 self.bridge.register("actionResult", self.actionResult, "request")
71 self.bridge.register("actionResultExt", self.actionResult, "request")
72
73 self.current_action_ids = set()
74 self.current_action_ids_cb = {}
75
76 def check_profile(self, profile):
77 """Tell if the profile is currently followed by the application"""
78 return profile in self.profiles.keys()
79
80 def postInit(self):
81 """Must be called after initialization is done, do all automatic task (auto plug profile)"""
82 if self.options.profile:
83 if not self.bridge.getProfileName(self.options.profile):
84 error(_("Trying to plug an unknown profile (%s)" % self.options.profile))
85 else:
86 self.plug_profile(self.options.profile)
87
88 def check_options(self):
89 """Check command line options"""
90 usage=_("""
91 %prog [options]
92
93 %prog --help for options list
94 """)
95 parser = OptionParser(usage=usage)
96
97 parser.add_option("-p", "--profile", help=_("Select the profile to use"))
98
99 (self.options, args) = parser.parse_args()
100 if self.options.profile:
101 self.options.profile = self.options.profile.decode('utf-8')
102 return args
103
104 def plug_profile(self, profile_key='@DEFAULT@'):
105 """Tell application which profile must be used"""
106 if self.single_profile and self.profiles:
107 error(_('There is already one profile plugged (we are in single profile mode) !'))
108 return
109 profile = self.bridge.getProfileName(profile_key)
110 if not profile:
111 error(_("The profile asked doesn't exist"))
112 return
113 if self.profiles.has_key(profile):
114 warning(_("The profile is already plugged"))
115 return
116 self.profiles[profile]={}
117 if self.single_profile:
118 self.profile = profile
119
120 ###now we get the essential params###
121 self.profiles[profile]['whoami']=JID(self.bridge.getParamA("JabberID","Connection", profile))
122 autoconnect = self.bridge.getParamA("autoconnect","Connection", profile) == "true"
123 self.profiles[profile]['watched']=self.bridge.getParamA("Watched", "Misc", profile).split() #TODO: put this in a plugin
124
125 ## misc ##
126 self.profiles[profile]['onlineContact'] = set() #FIXME: temporary
127
128 #TODO: gof: manage multi-profiles here
129 if not self.bridge.isConnected(profile):
130 self.setStatusOnline(False)
131 else:
132 self.setStatusOnline(True)
133
134 ### now we fill the contact list ###
135 for contact in self.bridge.getContacts(profile):
136 self.newContact(contact[0], contact[1], contact[2], profile)
137
138 presences = self.bridge.getPresenceStatus(profile)
139 for contact in presences:
140 for res in presences[contact]:
141 jabber_id = contact+('/'+res if res else '')
142 show = presences[contact][res][0]
143 priority = presences[contact][res][1]
144 statuses = presences[contact][res][2]
145 self.presenceUpdate(jabber_id, show, priority, statuses, profile)
146
147 #The waiting subscription requests
148 waitingSub = self.bridge.getWaitingSub(profile)
149 for sub in waitingSub:
150 self.subscribe(waitingSub[sub], sub, profile)
151
152 #Now we open the MUC window where we already are:
153 for room_args in self.bridge.getRoomJoined(profile):
154 self.roomJoined(*room_args, profile=profile)
155
156 for subject_args in self.bridge.getRoomSubjects(profile):
157 self.roomNewSubject(*subject_args, profile=profile)
158
159 if autoconnect and not self.bridge.isConnected(profile_key):
160 #Does the user want autoconnection ?
161 self.bridge.connect(profile_key)
162
163
164 def unplug_profile(self, profile):
165 """Tell the application to not follow anymore the profile"""
166 if not profile in self.profiles:
167 warning (_("This profile is not plugged"))
168 return
169 self.profiles.remove(profile)
170
171 def clear_profile(self):
172 self.profiles.clear()
173
174 def connected(self, profile):
175 """called when the connection is made"""
176 if not self.check_profile(profile):
177 return
178 debug(_("Connected"))
179 self.setStatusOnline(True)
180
181
182 def disconnected(self, profile):
183 """called when the connection is closed"""
184 if not self.check_profile(profile):
185 return
186 debug(_("Disconnected"))
187 self.CM.clear()
188 self.contactList.clear_contacts()
189 self.setStatusOnline(False)
190
191 def newContact(self, JabberId, attributes, groups, profile):
192 if not self.check_profile(profile):
193 return
194 entity=JID(JabberId)
195 self.rosterList[entity.short]=(dict(attributes), list(groups))
196
197 def newMessage(self, from_jid, msg, type, to_jid, profile):
198 if not self.check_profile(profile):
199 return
200 sender=JID(from_jid)
201 addr=JID(to_jid)
202 win = addr if sender.short == self.profiles[profile]['whoami'].short else sender
203 self.current_action_ids = set()
204 self.current_action_ids_cb = {}
205 self.chat_wins[win.short].printMessage(sender, msg, profile)
206
207 def newAlert(self, msg, title, alert_type, profile):
208 if not self.check_profile(profile):
209 return
210 assert alert_type in ['INFO','ERROR']
211 self.showDialog(unicode(msg),unicode(title),alert_type.lower())
212
213
214 def setStatusOnline(self, online=True):
215 pass
216
217 def presenceUpdate(self, jabber_id, show, priority, statuses, profile):
218 if not self.check_profile(profile):
219 return
220 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});
221 from_jid=JID(jabber_id)
222 debug ("from_jid.short=%(from_jid)s whoami.short=%(whoami)s" % {'from_jid':from_jid.short, 'whoami':self.profiles[profile]['whoami'].short})
223
224 if from_jid.short==self.profiles[profile]['whoami'].short:
225 if not type:
226 self.setStatusOnline(True)
227 elif type=="unavailable":
228 self.setStatusOnline(False)
229 return
230
231 if show != 'unavailable':
232 name=""
233 groups = []
234 if self.rosterList.has_key(from_jid.short):
235 if self.rosterList[from_jid.short][0].has_key("name"):
236 name=self.rosterList[from_jid.short][0]["name"]
237 groups=self.rosterList[from_jid.short][1]
238
239 #FIXME: must be moved in a plugin
240 if from_jid.short in self.profiles[profile]['watched'] and not from_jid.short in self.profiles[profile]['onlineContact']:
241 self.showAlert(_("Watched jid [%s] is connected !") % from_jid.short)
242
243 self.profiles[profile]['onlineContact'].add(from_jid) #FIXME onlineContact is useless with CM, must be removed
244 self.CM.add(from_jid)
245 self.CM.update(from_jid, 'name', name)
246 self.CM.update(from_jid, 'show', show)
247 self.CM.update(from_jid, 'statuses', statuses)
248 self.CM.update(from_jid, 'groups', groups)
249 cache = self.bridge.getCardCache(from_jid)
250 if cache.has_key('nick'):
251 self.CM.update(from_jid, 'nick', cache['nick'])
252 if cache.has_key('avatar'):
253 self.CM.update(from_jid, 'avatar', self.bridge.getAvatarFile(cache['avatar']))
254 self.contactList.replace(from_jid, self.CM.getAttr(from_jid, 'groups'))
255
256 if show=="unavailable" and from_jid in self.profiles[profile]['onlineContact']:
257 self.profiles[profile]['onlineContact'].remove(from_jid)
258 self.CM.remove(from_jid)
259 if not self.CM.isConnected(from_jid):
260 self.contactList.disconnect(from_jid)
261
262 def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile):
263 """Called when a MUC room is joined"""
264 if not self.check_profile(profile):
265 return
266 debug (_("Room [%(room_name)s] joined by %(profile)s, users presents:%(users)s") % {'room_name':room_id+'@'+room_service, 'profile': profile, 'users':room_nicks})
267 room_jid=room_id+'@'+room_service
268 self.chat_wins[room_jid].setUserNick(user_nick)
269 self.chat_wins[room_jid].setType("group")
270 self.chat_wins[room_jid].id = room_jid
271 self.chat_wins[room_jid].setPresents(list(set([user_nick]+room_nicks)))
272
273
274 def roomUserJoined(self, room_id, room_service, user_nick, user_data, profile):
275 """Called when an user joined a MUC room"""
276 if not self.check_profile(profile):
277 return
278 room_jid=room_id+'@'+room_service
279 if self.chat_wins.has_key(room_jid):
280 self.chat_wins[room_jid].replaceUser(user_nick)
281 debug (_("user [%(user_nick)s] joined room [%(room_jid)s]") % {'user_nick':user_nick, 'room_jid':room_jid})
282
283 def roomUserLeft(self, room_id, room_service, user_nick, user_data, profile):
284 """Called when an user joined a MUC room"""
285 if not self.check_profile(profile):
286 return
287 room_jid=room_id+'@'+room_service
288 if self.chat_wins.has_key(room_jid):
289 self.chat_wins[room_jid].removeUser(user_nick)
290 debug (_("user [%(user_nick)s] left room [%(room_jid)s]") % {'user_nick':user_nick, 'room_jid':room_jid})
291
292 def roomNewSubject(self, room_id, room_service, subject, profile):
293 """Called when subject of MUC room change"""
294 if not self.check_profile(profile):
295 return
296 room_jid=room_id+'@'+room_service
297 if self.chat_wins.has_key(room_jid):
298 self.chat_wins[room_jid].setSubject(subject)
299 debug (_("new subject for room [%(room_jid)s]: %(subject)s") % {'room_jid':room_jid, "subject":subject})
300
301 def tarotGameStarted(self, room_jid, referee, players, profile):
302 if not self.check_profile(profile):
303 return
304 debug (_("Tarot Game Started \o/"))
305 if self.chat_wins.has_key(room_jid):
306 self.chat_wins[room_jid].startGame("Tarot", referee, players)
307 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]})
308
309 def tarotGameNew(self, room_jid, hand, profile):
310 if not self.check_profile(profile):
311 return
312 debug (_("New Tarot Game"))
313 if self.chat_wins.has_key(room_jid):
314 self.chat_wins[room_jid].getGame("Tarot").newGame(hand)
315
316 def tarotChooseContrat(self, room_jid, xml_data, profile):
317 """Called when the player has to select his contrat"""
318 if not self.check_profile(profile):
319 return
320 debug (_("Tarot: need to select a contrat"))
321 if self.chat_wins.has_key(room_jid):
322 self.chat_wins[room_jid].getGame("Tarot").chooseContrat(xml_data)
323
324 def tarotShowCards(self, room_jid, game_stage, cards, data, profile):
325 if not self.check_profile(profile):
326 return
327 debug (_("Show cards"))
328 if self.chat_wins.has_key(room_jid):
329 self.chat_wins[room_jid].getGame("Tarot").showCards(game_stage, cards, data)
330
331 def tarotMyTurn(self, room_jid, profile):
332 if not self.check_profile(profile):
333 return
334 debug (_("My turn to play"))
335 if self.chat_wins.has_key(room_jid):
336 self.chat_wins[room_jid].getGame("Tarot").myTurn()
337
338 def tarotScore(self, room_jid, xml_data, winners, loosers, profile):
339 """Called when the game is finished and the score are updated"""
340 if not self.check_profile(profile):
341 return
342 debug (_("Tarot: score received"))
343 if self.chat_wins.has_key(room_jid):
344 self.chat_wins[room_jid].getGame("Tarot").showScores(xml_data, winners, loosers)
345
346 def tarotCardsPlayed(self, room_jid, player, cards, profile):
347 if not self.check_profile(profile):
348 return
349 debug (_("Card(s) played (%(player)s): %(cards)s") % {"player":player, "cards":cards})
350 if self.chat_wins.has_key(room_jid):
351 self.chat_wins[room_jid].getGame("Tarot").cardsPlayed(player, cards)
352
353 def tarotInvalidCards(self, room_jid, phase, played_cards, invalid_cards, profile):
354 if not self.check_profile(profile):
355 return
356 debug (_("Cards played are not valid: %s") % invalid_cards)
357 if self.chat_wins.has_key(room_jid):
358 self.chat_wins[room_jid].getGame("Tarot").invalidCards(phase, played_cards, invalid_cards)
359
360 def _subscribe_cb(self, answer, data):
361 entity, profile = data
362 if answer:
363 self.bridge.subscription("subscribed", entity.short, profile_key = profile)
364 else:
365 self.bridge.subscription("unsubscribed", entity.short, profile_key = profile)
366
367 def subscribe(self, type, raw_jid, profile):
368 """Called when a subsciption management signal is received"""
369 if not self.check_profile(profile):
370 return
371 entity = JID(raw_jid)
372 if type=="subscribed":
373 # this is a subscription confirmation, we just have to inform user
374 self.showDialog(_("The contact %s has accepted your subscription") % entity.short, _('Subscription confirmation'))
375 elif type=="unsubscribed":
376 # this is a subscription refusal, we just have to inform user
377 self.showDialog(_("The contact %s has refused your subscription") % entity.short, _('Subscription refusal'), 'error')
378 elif type=="subscribe":
379 # this is a subscriptionn request, we have to ask for user confirmation
380 answer = self.showDialog(_("The contact %s wants to subscribe to your presence.\nDo you accept ?") % entity.short, _('Subscription confirmation'), 'yes/no', answer_cb = self._subscribe_cb, answer_data=(entity, profile))
381
382 def showDialog(self, message, title, type="info", answer_cb = None):
383 raise NotImplementedError
384
385 def showAlert(self, message):
386 pass #FIXME
387
388 def paramUpdate(self, name, value, namespace, profile):
389 if not self.check_profile(profile):
390 return
391 debug(_("param update: [%(namespace)s] %(name)s = %(value)s") % {'namespace':namespace, 'name':name, 'value':value})
392 if (namespace,name) == ("Connection", "JabberID"):
393 debug (_("Changing JID to %s"), value)
394 self.profiles[profile]['whoami']=JID(value)
395 elif (namespace,name) == ("Misc", "Watched"):
396 self.profiles[profile]['watched']=value.split()
397
398 def contactDeleted(self, jid, profile):
399 if not self.check_profile(profile):
400 return
401 target = JID(jid)
402 self.CM.remove(target)
403 self.contactList.remove(self.CM.get_full(target))
404 try:
405 self.profiles[profile]['onlineContact'].remove(target.short)
406 except KeyError:
407 pass
408
409 def updatedValue(self, name, data):
410 if name == "card_nick":
411 target = JID(data['jid'])
412 if target in self.contactList:
413 self.CM.update(target, 'nick', data['nick'])
414 self.contactList.replace(target)
415 elif name == "card_avatar":
416 target = JID(data['jid'])
417 if target in self.contactList:
418 filename = self.bridge.getAvatarFile(data['avatar'])
419 self.CM.update(target, 'avatar', filename)
420 self.contactList.replace(target)
421
422 def askConfirmation(self, type, id, data):
423 raise NotImplementedError
424
425 def actionResult(self, type, id, data):
426 raise NotImplementedError
427
428 def onExit(self):
429 """Must be called when the frontend is terminating"""
430 #TODO: mange multi-profile here
431 try:
432 autodisconnect = self.bridge.getParamA("autodisconnect","Connection", self.profile) == "true"
433 if autodisconnect and self.bridge.isConnected(self.profile):
434 #Does the user want autodisconnection ?
435 self.bridge.disconnect(self.profile)
436 except:
437 pass