comparison src/plugins/plugin_misc_radiocol.py @ 746:539f278bc265

plugin room_games, radiocol: send the current queue to new players
author souliane <souliane@mailoo.org>
date Thu, 28 Nov 2013 19:23:59 +0100
parents 074970227bc0
children d0e809014ea2
comparison
equal deleted inserted replaced
745:812dc38c0094 746:539f278bc265
21 from twisted.words.xish import domish 21 from twisted.words.xish import domish
22 from twisted.internet import reactor 22 from twisted.internet import reactor
23 from twisted.words.protocols.jabber import jid 23 from twisted.words.protocols.jabber import jid
24 24
25 import os.path 25 import os.path
26 import copy
27 import time
26 from os import unlink 28 from os import unlink
27 from mutagen.oggvorbis import OggVorbis, OggVorbisHeaderError 29 from mutagen.oggvorbis import OggVorbis, OggVorbisHeaderError
28 30
29 31
30 NC_RADIOCOL = 'http://www.goffi.org/protocol/radiocol' 32 NC_RADIOCOL = 'http://www.goffi.org/protocol/radiocol'
39 "main": "Radiocol", 41 "main": "Radiocol",
40 "handler": "yes", 42 "handler": "yes",
41 "description": _("""Implementation of radio collective""") 43 "description": _("""Implementation of radio collective""")
42 } 44 }
43 45
46
47 # Number of songs needed in the queue before we start playing
48 QUEUE_TO_START = 2
49 # Maximum number of songs in the queue (the song being currently played doesn't count)
44 QUEUE_LIMIT = 2 50 QUEUE_LIMIT = 2
45 51
46 52
47 class Radiocol(object): 53 class Radiocol(object):
48 54
59 self.host = host 65 self.host = host
60 host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='asss', out_sign='', method=self.prepareRoom) 66 host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='asss', out_sign='', method=self.prepareRoom)
61 host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='sass', out_sign='', method=self.createGame) 67 host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='sass', out_sign='', method=self.createGame)
62 host.bridge.addMethod("radiocolSongAdded", ".plugin", in_sign='sss', out_sign='', method=self.radiocolSongAdded) 68 host.bridge.addMethod("radiocolSongAdded", ".plugin", in_sign='sss', out_sign='', method=self.radiocolSongAdded)
63 host.bridge.addSignal("radiocolPlayers", ".plugin", signature='ssass') # room_jid, referee, players, profile 69 host.bridge.addSignal("radiocolPlayers", ".plugin", signature='ssass') # room_jid, referee, players, profile
64 host.bridge.addSignal("radiocolStarted", ".plugin", signature='ssass') # room_jid, referee, players, profile 70 host.bridge.addSignal("radiocolStarted", ".plugin", signature='ssasais') # room_jid, referee, players, [QUEUE_TO_START, QUEUE_LIMIT], profile
65 host.bridge.addSignal("radiocolSongRejected", ".plugin", signature='sss') # room_jid, reason, profile 71 host.bridge.addSignal("radiocolSongRejected", ".plugin", signature='sss') # room_jid, reason, profile
66 host.bridge.addSignal("radiocolPreload", ".plugin", signature='ssssss') # room_jid, filename, title, artist, album, profile 72 host.bridge.addSignal("radiocolPreload", ".plugin", signature='sssssss') # room_jid, timestamp, filename, title, artist, album, profile
67 host.bridge.addSignal("radiocolPlay", ".plugin", signature='sss') # room_jid, filename, profile 73 host.bridge.addSignal("radiocolPlay", ".plugin", signature='sss') # room_jid, filename, profile
68 host.bridge.addSignal("radiocolNoUpload", ".plugin", signature='ss') # room_jid, profile 74 host.bridge.addSignal("radiocolNoUpload", ".plugin", signature='ss') # room_jid, profile
69 host.bridge.addSignal("radiocolUploadOk", ".plugin", signature='ss') # room_jid, profile 75 host.bridge.addSignal("radiocolUploadOk", ".plugin", signature='ss') # room_jid, profile
70 76
71 def __create_preload_elt(self, sender, filename, title, artist, album): 77 def __create_preload_elt(self, sender, song_added_elt):
72 preload_elt = domish.Element((None, 'preload')) 78 preload_elt = copy.deepcopy(song_added_elt)
79 preload_elt.name = 'preload'
73 preload_elt['sender'] = sender 80 preload_elt['sender'] = sender
74 preload_elt['filename'] = filename # XXX: the frontend should know the temporary directory where file is put 81 preload_elt['timestamp'] = str(time.time())
75 preload_elt['title'] = title 82 # attributes filename, title, artist, album, length have been copied
76 preload_elt['artist'] = artist 83 # XXX: the frontend should know the temporary directory where file is put
77 preload_elt['album'] = album
78 return preload_elt 84 return preload_elt
79 85
80 def radiocolSongAdded(self, referee, song_path, profile): 86 def radiocolSongAdded(self, referee, song_path, profile):
81 """This method is called by libervia when a song has been uploaded 87 """This method is called by libervia when a song has been uploaded
82 @param room_jid_param: jid of the room 88 @param room_jid_param: jid of the room
92 return 98 return
93 try: 99 try:
94 song = OggVorbis(song_path) 100 song = OggVorbis(song_path)
95 except OggVorbisHeaderError: 101 except OggVorbisHeaderError:
96 #this file is not ogg vorbis, we reject it 102 #this file is not ogg vorbis, we reject it
97 unlink(song_path) # FIXME: same host trick (see note above) 103 self.deleteFile(song_path) # FIXME: same host trick (see note above)
98 self.host.bridge.radiocolSongRejected(jid.JID(referee).userhost(), 104 self.host.bridge.radiocolSongRejected(jid.JID(referee).userhost(),
99 "Uploaded file is not Ogg Vorbis song, only Ogg Vorbis songs are acceptable", profile) 105 "Uploaded file is not Ogg Vorbis song, only Ogg Vorbis songs are acceptable", profile)
100 """mess = self.createGameElt(jid.JID(referee)) 106 """mess = self.createGameElt(jid.JID(referee))
101 reject_elt = mess.firstChildElement().addElement(('','song_rejected')) 107 reject_elt = mess.firstChildElement().addElement(('','song_rejected'))
102 reject_elt['sender'] = client.jid 108 reject_elt['sender'] = client.jid
114 120
115 radio_data = self.games[jid.JID(referee).userhost()] # FIXME: referee comes from Libervia's client side, it's unsecure 121 radio_data = self.games[jid.JID(referee).userhost()] # FIXME: referee comes from Libervia's client side, it's unsecure
116 radio_data['to_delete'][attrs['filename']] = song_path # FIXME: works only because of the same host trick, see the note under the docstring 122 radio_data['to_delete'][attrs['filename']] = song_path # FIXME: works only because of the same host trick, see the note under the docstring
117 123
118 def playNext(self, room_jid, profile): 124 def playNext(self, room_jid, profile):
119 """"Play next sont in queue if exists, and put a timer 125 """"Play next song in queue if exists, and put a timer
120 which trigger after the song has been played to play next one""" 126 which trigger after the song has been played to play next one"""
121 #TODO: need to check that there are still peoples in the room
122 # and clean the datas/stop the playlist if it's not the case
123 #TODO: songs need to be erased once played or found invalids 127 #TODO: songs need to be erased once played or found invalids
124 # ==> unlink done the Q&D way with the same host trick (see above) 128 # ==> unlink done the Q&D way with the same host trick (see above)
125 radio_data = self.games[room_jid.userhost()] 129 radio_data = self.games[room_jid.userhost()]
130 if len(radio_data['players']) == 0:
131 debug(_('No more participants in the radiocol: cleaning data'))
132 radio_data['queue'] = []
133 for filename in radio_data['to_delete']:
134 self.deleteFile(radio_data, filename)
135 radio_data['to_delete'] = {}
126 queue = radio_data['queue'] 136 queue = radio_data['queue']
127 if not queue: 137 if not queue:
128 #nothing left to play, we need to wait for uploads 138 #nothing left to play, we need to wait for uploads
129 radio_data['playing'] = False 139 radio_data['playing'] = False
130 return 140 return
131 141
132 filename, length = queue.pop(0) 142 song = queue.pop(0)
143 filename, length = song['filename'], float(song['length'])
133 self.send(room_jid, ('', 'play'), {'filename': filename}, profile=profile) 144 self.send(room_jid, ('', 'play'), {'filename': filename}, profile=profile)
134 145
135 if not radio_data['upload'] and len(queue) < QUEUE_LIMIT: 146 if not radio_data['upload'] and len(queue) < QUEUE_LIMIT:
136 #upload is blocked and we now have resources to get more, we reactivate it 147 #upload is blocked and we now have resources to get more, we reactivate it
137 self.send(room_jid, ('', 'upload_ok'), profile=profile) 148 self.send(room_jid, ('', 'upload_ok'), profile=profile)
138 radio_data['upload'] = True 149 radio_data['upload'] = True
139 150
140 reactor.callLater(length, self.playNext, room_jid, profile) 151 reactor.callLater(length, self.playNext, room_jid, profile)
152 #we wait more than the song length to delete the file, to manage poorly reactive networks/clients
153 reactor.callLater(length + 90, self.deleteFile, radio_data, filename) # FIXME: same host trick (see above)
154
155 def deleteFile(self, radio_data, filename):
141 try: 156 try:
142 file_to_delete = radio_data['to_delete'][filename] 157 file_to_delete = radio_data['to_delete'][filename]
143 except KeyError: 158 except KeyError:
144 error(_("INTERNAL ERROR: can't find full path of the song to delete")) 159 error(_("INTERNAL ERROR: can't find full path of the song to delete"))
145 return 160 return False
146 161 try:
147 #we wait more than the song length to delete the file, to manage poorly reactive networks/clients 162 unlink(file_to_delete)
148 reactor.callLater(length + 90, unlink, file_to_delete) # FIXME: same host trick (see above) 163 except OSError:
164 error(_("INTERNAL ERROR: can't find %s on the file system" % file_to_delete))
165 return False
166 return True
149 167
150 def room_game_cmd(self, mess_elt, profile): 168 def room_game_cmd(self, mess_elt, profile):
151 #FIXME: we should check sender (is it referee ?) here before accepting commands
152 from_jid = jid.JID(mess_elt['from']) 169 from_jid = jid.JID(mess_elt['from'])
153 room_jid = jid.JID(from_jid.userhost()) 170 room_jid = jid.JID(from_jid.userhost())
154 radio_elt = mess_elt.firstChildElement() 171 radio_elt = mess_elt.firstChildElement()
155 radio_data = self.games[room_jid.userhost()] 172 radio_data = self.games[room_jid.userhost()]
156 if 'queue' in radio_data: 173 if 'queue' in radio_data:
157 queue = radio_data['queue'] 174 queue = radio_data['queue']
158 175
176 from_referee = self.isReferee(room_jid.userhost(), from_jid.resource)
177 to_referee = self.isReferee(room_jid.userhost(), jid.JID(mess_elt['to']).user)
159 for elt in radio_elt.elements(): 178 for elt in radio_elt.elements():
179 if not from_referee and not (to_referee and elt.name == 'song_added'):
180 continue # sender must be referee, expect when a song is submitted
160 181
161 if elt.name == 'started' or elt.name == 'players': # new game created 182 if elt.name == 'started' or elt.name == 'players': # new game created
162 players = [] 183 players = []
163 for player in elt.elements(): 184 for player in elt.elements():
164 players.append(unicode(player)) 185 players.append(unicode(player))
165 signal = self.host.bridge.radiocolStarted if elt.name == 'started' else self.host.bridge.radiocolPlayers 186 signal = self.host.bridge.radiocolStarted if elt.name == 'started' else self.host.bridge.radiocolPlayers
166 signal(room_jid.userhost(), from_jid.full(), players, profile) 187 signal(room_jid.userhost(), from_jid.full(), players, [QUEUE_TO_START, QUEUE_LIMIT], profile)
167 elif elt.name == 'preload': # a song is in queue and must be preloaded 188 elif elt.name == 'preload': # a song is in queue and must be preloaded
168 self.host.bridge.radiocolPreload(room_jid.userhost(), elt['filename'], elt['title'], elt['artist'], elt['album'], profile) 189 self.host.bridge.radiocolPreload(room_jid.userhost(), elt['timestamp'], elt['filename'], elt['title'], elt['artist'], elt['album'], profile)
169 elif elt.name == 'play': 190 elif elt.name == 'play':
170 self.host.bridge.radiocolPlay(room_jid.userhost(), elt['filename'], profile) 191 self.host.bridge.radiocolPlay(room_jid.userhost(), elt['filename'], profile)
171 elif elt.name == 'song_rejected': # a song has been refused 192 elif elt.name == 'song_rejected': # a song has been refused
172 self.host.bridge.radiocolSongRejected(room_jid.userhost(), elt['reason'], profile) 193 self.host.bridge.radiocolSongRejected(room_jid.userhost(), elt['reason'], profile)
173 elif elt.name == 'no_upload': 194 elif elt.name == 'no_upload':
178 #FIXME: we are KISS for the proof of concept: every song is added, to a limit of 3 in queue. 199 #FIXME: we are KISS for the proof of concept: every song is added, to a limit of 3 in queue.
179 # Need to manage some sort of rules to allow peoples to send songs 200 # Need to manage some sort of rules to allow peoples to send songs
180 201
181 if len(queue) >= QUEUE_LIMIT: 202 if len(queue) >= QUEUE_LIMIT:
182 #there are already too many songs in queue, we reject this one 203 #there are already too many songs in queue, we reject this one
183 attrs = {'sender': from_jid.resource,
184 'reason': "Too many songs in queue"
185 }
186 #FIXME: add an error code 204 #FIXME: add an error code
187 self.send(room_jid, ('', 'song_rejected'), attrs, profile=profile) 205 self.send(from_jid, ('', 'song_rejected'), {'reason': "Too many songs in queue"}, profile=profile)
188 return 206 return
189 207
190 #The song is accepted and added in queue 208 #The song is accepted and added in queue
191 queue.append((elt['filename'], float(elt['length']))) 209 preload_elt = self.__create_preload_elt(from_jid.resource, elt)
210 queue.append(preload_elt)
192 211
193 if len(queue) >= QUEUE_LIMIT: 212 if len(queue) >= QUEUE_LIMIT:
194 #We are at the limit, we refuse new upload until next play 213 #We are at the limit, we refuse new upload until next play
195 #FIXME: add an error code
196 self.send(room_jid, ('', 'no_upload'), profile=profile) 214 self.send(room_jid, ('', 'no_upload'), profile=profile)
197 radio_data['upload'] = False 215 radio_data['upload'] = False
198 216
199 preload_elt = self.__create_preload_elt(from_jid.resource,
200 elt['filename'],
201 elt['title'],
202 elt['artist'],
203 elt['album'])
204 self.send(room_jid, preload_elt, profile=profile) 217 self.send(room_jid, preload_elt, profile=profile)
205 if not radio_data['playing'] and len(queue) == 2: 218 if not radio_data['playing'] and len(queue) == QUEUE_TO_START:
206 #we have not started playing yet, and we have 2 songs in queue 219 # We have not started playing yet, and we have QUEUE_TO_START
207 #we can now start the party :) 220 # songs in queue. We can now start the party :)
208 radio_data['playing'] = True 221 radio_data['playing'] = True
209 self.playNext(room_jid, profile) 222 self.playNext(room_jid, profile)
210 else: 223 else:
211 error(_('Unmanaged game element: %s') % elt.name) 224 error(_('Unmanaged game element: %s') % elt.name)
225
226 def getSyncData(self, room_jid_s):
227 data = self.games[room_jid_s]['queue']
228 if len(data) == QUEUE_LIMIT:
229 data.append(domish.Element((None, 'no_upload')))
230 return data