comparison src/plugins/plugin_misc_radiocol.py @ 683:75e4f5e2cc65

plugins radiocol, card_game, quiz: code factorization
author souliane <souliane@mailoo.org>
date Wed, 23 Oct 2013 12:45:13 +0200
parents 84a6e83157c2
children f610864eb7a5
comparison
equal deleted inserted replaced
682:2805fa3f4bdf 683:75e4f5e2cc65
27 from zope.interface import implements 27 from zope.interface import implements
28 28
29 import os.path 29 import os.path
30 from os import unlink 30 from os import unlink
31 from mutagen.oggvorbis import OggVorbis, OggVorbisHeaderError 31 from mutagen.oggvorbis import OggVorbis, OggVorbisHeaderError
32 32 from sat.tools.plugins.games import RoomGame
33 try: 33 try:
34 from twisted.words.protocols.xmlstream import XMPPHandler 34 from twisted.words.protocols.xmlstream import XMPPHandler
35 except ImportError: 35 except ImportError:
36 from wokkel.subprotocols import XMPPHandler 36 from wokkel.subprotocols import XMPPHandler
37 37
52 } 52 }
53 53
54 QUEUE_LIMIT = 2 54 QUEUE_LIMIT = 2
55 55
56 56
57 class Radiocol(object): 57 class Radiocol(RoomGame):
58 58
59 def __init__(self, host): 59 def __init__(self, host):
60 info(_("Radio collective initialization")) 60 info(_("Radio collective initialization"))
61 RoomGame.__init__(self, host, PLUGIN_INFO, (NC_RADIOCOL, RADIOC_TAG),
62 options={'queue': [], 'upload': True, 'playing': False, 'to_delete': {}})
61 self.host = host 63 self.host = host
62 self.radios = {} 64 host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='ass', out_sign='', method=self.prepareRoom)
63 host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='ass', out_sign='', method=self.radiocolLaunch) 65 host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='ss', out_sign='', method=self.createCollectiveGame)
64 host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='ss', out_sign='', method=self.radiocolCreate)
65 host.bridge.addMethod("radiocolSongAdded", ".plugin", in_sign='sss', out_sign='', method=self.radiocolSongAdded) 66 host.bridge.addMethod("radiocolSongAdded", ".plugin", in_sign='sss', out_sign='', method=self.radiocolSongAdded)
66 host.bridge.addSignal("radiocolStarted", ".plugin", signature='sss') # room_jid, referee, profile 67 host.bridge.addSignal("radiocolStarted", ".plugin", signature='sss') # room_jid, referee, profile
67 host.bridge.addSignal("radiocolSongRejected", ".plugin", signature='sss') # room_jid, reason, profile 68 host.bridge.addSignal("radiocolSongRejected", ".plugin", signature='sss') # room_jid, reason, profile
68 host.bridge.addSignal("radiocolPreload", ".plugin", signature='ssssss') # room_jid, filename, title, artist, album, profile 69 host.bridge.addSignal("radiocolPreload", ".plugin", signature='ssssss') # room_jid, filename, title, artist, album, profile
69 host.bridge.addSignal("radiocolPlay", ".plugin", signature='sss') # room_jid, filename, profile 70 host.bridge.addSignal("radiocolPlay", ".plugin", signature='sss') # room_jid, filename, profile
70 host.bridge.addSignal("radiocolNoUpload", ".plugin", signature='ss') # room_jid, profile 71 host.bridge.addSignal("radiocolNoUpload", ".plugin", signature='ss') # room_jid, profile
71 host.bridge.addSignal("radiocolUploadOk", ".plugin", signature='ss') # room_jid, profile 72 host.bridge.addSignal("radiocolUploadOk", ".plugin", signature='ss') # room_jid, profile
72 host.trigger.add("MUC user joined", self.userJoinedTrigger) 73 host.trigger.add("MUC user joined", self.userJoinedTrigger)
73 74
74 def createRadiocolElt(self, to_jid, type="normal"):
75 type = "normal" if to_jid.resource else "groupchat"
76 elt = domish.Element((None, 'message'))
77 elt["to"] = to_jid.full()
78 elt["type"] = type
79 elt.addElement((NC_RADIOCOL, RADIOC_TAG))
80 return elt
81
82 def __create_started_elt(self):
83 """Create a game_started domish element"""
84 started_elt = domish.Element((None, 'started'))
85 return started_elt
86
87 def __create_preload_elt(self, sender, filename, title, artist, album): 75 def __create_preload_elt(self, sender, filename, title, artist, album):
88 preload_elt = domish.Element((None, 'preload')) 76 preload_elt = domish.Element((None, 'preload'))
89 preload_elt['sender'] = sender 77 preload_elt['sender'] = sender
90 preload_elt['filename'] = filename # XXX: the frontend should know the temporary directory where file is put 78 preload_elt['filename'] = filename # XXX: the frontend should know the temporary directory where file is put
91 preload_elt['title'] = title 79 preload_elt['title'] = title
92 preload_elt['artist'] = artist 80 preload_elt['artist'] = artist
93 preload_elt['album'] = album 81 preload_elt['album'] = album
94 return preload_elt 82 return preload_elt
95
96 def userJoinedTrigger(self, room, user, profile):
97 """This trigger is used to check if we are waiting people in this room,
98 and to create a game if everybody is here"""
99 room_jid = room.occupantJID.userhost()
100 if room_jid in self.radios and self.radios[room_jid]["referee"] == room.occupantJID.full():
101 #we are in a radiocol room, let's start the party !
102 mess = self.createRadiocolElt(jid.JID(room_jid + '/' + user.nick))
103 mess.firstChildElement().addChild(self.__create_started_elt())
104 self.host.profiles[profile].xmlstream.send(mess)
105 return True
106
107 def radiocolLaunch(self, occupants, profile_key='@DEFAULT@'):
108 """Launch a game: helper method to create a room, invite occupants, and create the radiocol
109 @param occupants: list for occupants jid"""
110 debug(_('Launching radiocol'))
111 profile = self.host.memory.getProfileName(profile_key)
112 if not profile:
113 error(_("Unknown profile"))
114 return
115
116 def radiocolRoomJoined(room):
117 print "radiocolRoomJoined"
118 _room_jid = room.occupantJID.userhostJID()
119 self.radiocolCreate(_room_jid.userhost(), profile_key=profile)
120 for occupant in occupants:
121 self.host.plugins["XEP-0249"].invite(jid.JID(occupant), room.occupantJID.userhostJID(), {"game": "Radiocol"}, profile)
122
123 def after_init(ignore):
124 room_name = "sat_radiocol_%s" % self.host.plugins["XEP-0045"].getUniqueName(profile_key)
125 print "\n\n===> room_name:", room_name
126 muc_service = None
127 for service in self.host.memory.getServerServiceEntities("conference", "text", profile):
128 if not ".irc." in service.userhost():
129 #FIXME:
130 #This awfull ugly hack is here to avoid an issue with openfire: the irc gateway
131 #use "conference/text" identity (instead of "conference/irc"), there is certainly a better way
132 #to manage this, but this hack fill do it for test purpose
133 muc_service = service
134 break
135 if not muc_service:
136 error(_("Can't find a MUC service"))
137 return
138
139 _jid, xmlstream = self.host.getJidNStream(profile)
140 d = self.host.plugins["XEP-0045"].join(jid.JID("%s@%s" % (room_name, muc_service.userhost())), _jid.user, {}, profile)
141 d.addCallback(radiocolRoomJoined)
142
143 client = self.host.getClient(profile)
144 if not client:
145 error(_('No client for this profile key: %s') % profile_key)
146 return
147 client.client_initialized.addCallback(after_init)
148
149 def radiocolCreate(self, room_jid_param, profile_key='@DEFAULT@'):
150 """Create a new game
151 @param room_jid_param: jid of the room
152 @param profile_key: %(doc_profile_key)s"""
153 debug(_("Creating Radiocol"))
154 room_jid = jid.JID(room_jid_param)
155 profile = self.host.memory.getProfileName(profile_key)
156 if not profile:
157 error(_("profile %s is unknown") % profile_key)
158 return
159 if room_jid in self.radios:
160 warning(_("Radiocol already started in room %s") % room_jid.userhost())
161 else:
162 room_nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid.userhost(), profile)
163 if not room_nick:
164 error('Internal error')
165 return
166 referee = room_jid.userhost() + '/' + room_nick
167 self.radios[room_jid.userhost()] = {'referee': referee, 'queue': [], 'upload': True, 'playing': False, 'occupants_data': {}, 'to_delete': {}}
168 mess = self.createRadiocolElt(jid.JID(room_jid.userhost()))
169 mess.firstChildElement().addChild(self.__create_started_elt())
170 self.host.profiles[profile].xmlstream.send(mess)
171 83
172 def radiocolSongAdded(self, referee, song_path, profile): 84 def radiocolSongAdded(self, referee, song_path, profile):
173 """This method is called by libervia when a song has been uploaded 85 """This method is called by libervia when a song has been uploaded
174 @param room_jid_param: jid of the room 86 @param room_jid_param: jid of the room
175 @song_path: absolute path of the song added 87 @song_path: absolute path of the song added
187 except OggVorbisHeaderError: 99 except OggVorbisHeaderError:
188 #this file is not ogg vorbis, we reject it 100 #this file is not ogg vorbis, we reject it
189 unlink(song_path) # FIXME: same host trick (see note above) 101 unlink(song_path) # FIXME: same host trick (see note above)
190 self.host.bridge.radiocolSongRejected(jid.JID(referee).userhost(), 102 self.host.bridge.radiocolSongRejected(jid.JID(referee).userhost(),
191 "Uploaded file is not Ogg Vorbis song, only Ogg Vorbis songs are acceptable", profile) 103 "Uploaded file is not Ogg Vorbis song, only Ogg Vorbis songs are acceptable", profile)
192 """mess = self.createRadiocolElt(jid.JID(referee)) 104 """mess = self.createGameElt(jid.JID(referee))
193 reject_elt = mess.firstChildElement().addElement(('','song_rejected')) 105 reject_elt = mess.firstChildElement().addElement(('','song_rejected'))
194 reject_elt['sender'] = client.jid 106 reject_elt['sender'] = client.jid
195 reject_elt['reason'] = "Uploaded file is not Ogg Vorbis song, only Ogg Vorbis songs are acceptable" 107 reject_elt['reason'] = "Uploaded file is not Ogg Vorbis song, only Ogg Vorbis songs are acceptable"
196 #FIXME: add an error code 108 #FIXME: add an error code
197 self.host.profiles[profile].xmlstream.send(mess)""" 109 self.host.profiles[profile].xmlstream.send(mess)"""
198 return 110 return
199 title = song.get("title", ["Unknown"])[0] 111 title = song.get("title", ["Unknown"])[0]
200 artist = song.get("artist", ["Unknown"])[0] 112 artist = song.get("artist", ["Unknown"])[0]
201 album = song.get("album", ["Unknown"])[0] 113 album = song.get("album", ["Unknown"])[0]
202 length = song.info.length 114 length = song.info.length
203 mess = self.createRadiocolElt(jid.JID(referee)) 115 mess = self.createGameElt(jid.JID(referee))
204 added_elt = mess.firstChildElement().addElement(('', 'song_added')) 116 added_elt = mess.firstChildElement().addElement(('', 'song_added'))
205 added_elt['filename'] = filename = os.path.basename(song_path) 117 added_elt['filename'] = filename = os.path.basename(song_path)
206 added_elt['title'] = title 118 added_elt['title'] = title
207 added_elt['artist'] = artist 119 added_elt['artist'] = artist
208 added_elt['album'] = album 120 added_elt['album'] = album
209 added_elt['length'] = str(length) 121 added_elt['length'] = str(length)
210 self.host.profiles[profile].xmlstream.send(mess) 122 self.host.profiles[profile].xmlstream.send(mess)
211 123
212 radio_data = self.radios[jid.JID(referee).userhost()] # FIXME: referee comes from Libervia's client side, it's unsecure 124 radio_data = self.games[jid.JID(referee).userhost()] # FIXME: referee comes from Libervia's client side, it's unsecure
213 radio_data['to_delete'][filename] = song_path # FIXME: works only because of the same host trick, see the note under the docstring 125 radio_data['to_delete'][filename] = song_path # FIXME: works only because of the same host trick, see the note under the docstring
214 126
215 def playNext(self, room_jid, profile): 127 def playNext(self, room_jid, profile):
216 """"Play next sont in queue if exists, and put a timer 128 """"Play next sont in queue if exists, and put a timer
217 which trigger after the song has been played to play next one""" 129 which trigger after the song has been played to play next one"""
218 #TODO: need to check that there are still peoples in the room 130 #TODO: need to check that there are still peoples in the room
219 # and clean the datas/stop the playlist if it's not the case 131 # and clean the datas/stop the playlist if it's not the case
220 #TODO: songs need to be erased once played or found invalids 132 #TODO: songs need to be erased once played or found invalids
221 # ==> unlink done the Q&D way with the same host trick (see above) 133 # ==> unlink done the Q&D way with the same host trick (see above)
222 radio_data = self.radios[room_jid.userhost()] 134 radio_data = self.games[room_jid.userhost()]
223 queue = radio_data['queue'] 135 queue = radio_data['queue']
224 if not queue: 136 if not queue:
225 #nothing left to play, we need to wait for uploads 137 #nothing left to play, we need to wait for uploads
226 radio_data['playing'] = False 138 radio_data['playing'] = False
227 return 139 return
228 140
229 filename, length = queue.pop(0) 141 filename, length = queue.pop(0)
230 mess = self.createRadiocolElt(room_jid) 142 mess = self.createGameElt(room_jid)
231 play_elt = mess.firstChildElement().addElement(('', 'play')) 143 play_elt = mess.firstChildElement().addElement(('', 'play'))
232 play_elt['filename'] = filename 144 play_elt['filename'] = filename
233 self.host.profiles[profile].xmlstream.send(mess) 145 self.host.profiles[profile].xmlstream.send(mess)
234 146
235 if not radio_data['upload'] and len(queue) < QUEUE_LIMIT: 147 if not radio_data['upload'] and len(queue) < QUEUE_LIMIT:
236 #upload is blocked and we now have resources to get more, we reactivate it 148 #upload is blocked and we now have resources to get more, we reactivate it
237 mess = self.createRadiocolElt(room_jid) 149 mess = self.createGameElt(room_jid)
238 no_upload_elt = mess.firstChildElement().addElement(('', 'upload_ok')) 150 no_upload_elt = mess.firstChildElement().addElement(('', 'upload_ok'))
239 self.host.profiles[profile].xmlstream.send(mess) 151 self.host.profiles[profile].xmlstream.send(mess)
240 radio_data['upload'] = True 152 radio_data['upload'] = True
241 153
242 reactor.callLater(length, self.playNext, room_jid, profile) 154 reactor.callLater(length, self.playNext, room_jid, profile)
252 def radiocol_game_cmd(self, mess_elt, profile): 164 def radiocol_game_cmd(self, mess_elt, profile):
253 #FIXME: we should check sender (is it referee ?) here before accepting commands 165 #FIXME: we should check sender (is it referee ?) here before accepting commands
254 from_jid = jid.JID(mess_elt['from']) 166 from_jid = jid.JID(mess_elt['from'])
255 room_jid = jid.JID(from_jid.userhost()) 167 room_jid = jid.JID(from_jid.userhost())
256 radio_elt = mess_elt.firstChildElement() 168 radio_elt = mess_elt.firstChildElement()
257 radio_data = self.radios[room_jid.userhost()] 169 radio_data = self.games[room_jid.userhost()]
258 occupants_data = radio_data['occupants_data']
259 queue = radio_data['queue'] 170 queue = radio_data['queue']
260 171
261 for elt in radio_elt.elements(): 172 for elt in radio_elt.elements():
262 173
263 if elt.name == 'started': # new game created 174 if elt.name == 'started': # new game created
276 #FIXME: we are KISS for the proof of concept: every song is added, to a limit of 3 in queue. 187 #FIXME: we are KISS for the proof of concept: every song is added, to a limit of 3 in queue.
277 # Need to manage some sort of rules to allow peoples to send songs 188 # Need to manage some sort of rules to allow peoples to send songs
278 189
279 if len(queue) >= QUEUE_LIMIT: 190 if len(queue) >= QUEUE_LIMIT:
280 #there are already too many songs in queue, we reject this one 191 #there are already too many songs in queue, we reject this one
281 mess = self.createRadiocolElt(room_jid) 192 mess = self.createGameElt(room_jid)
282 reject_elt = mess.firstChildElement().addElement(('', 'song_rejected')) 193 reject_elt = mess.firstChildElement().addElement(('', 'song_rejected'))
283 reject_elt['sender'] = from_jid.resource 194 reject_elt['sender'] = from_jid.resource
284 reject_elt['reason'] = "Too many songs in queue" 195 reject_elt['reason'] = "Too many songs in queue"
285 #FIXME: add an error code 196 #FIXME: add an error code
286 self.host.profiles[profile].xmlstream.send(mess) 197 self.host.profiles[profile].xmlstream.send(mess)
289 #The song is accepted and added in queue 200 #The song is accepted and added in queue
290 queue.append((elt['filename'], float(elt['length']))) 201 queue.append((elt['filename'], float(elt['length'])))
291 202
292 if len(queue) >= QUEUE_LIMIT: 203 if len(queue) >= QUEUE_LIMIT:
293 #We are at the limit, we refuse new upload until next play 204 #We are at the limit, we refuse new upload until next play
294 mess = self.createRadiocolElt(room_jid) 205 mess = self.createGameElt(room_jid)
295 no_upload_elt = mess.firstChildElement().addElement(('', 'no_upload')) 206 no_upload_elt = mess.firstChildElement().addElement(('', 'no_upload'))
296 #FIXME: add an error code 207 #FIXME: add an error code
297 self.host.profiles[profile].xmlstream.send(mess) 208 self.host.profiles[profile].xmlstream.send(mess)
298 radio_data['upload'] = False 209 radio_data['upload'] = False
299 210
300 mess = self.createRadiocolElt(room_jid) 211 mess = self.createGameElt(room_jid)
301 preload_elt = self.__create_preload_elt(from_jid.resource, 212 preload_elt = self.__create_preload_elt(from_jid.resource,
302 elt['filename'], 213 elt['filename'],
303 elt['title'], 214 elt['title'],
304 elt['artist'], 215 elt['artist'],
305 elt['album']) 216 elt['album'])