comparison sat/plugins/plugin_misc_radiocol.py @ 2624:56f94936df1e

code style reformatting using black
author Goffi <goffi@goffi.org>
date Wed, 27 Jun 2018 20:14:46 +0200
parents 26edcf3a30eb
children 003b8b4b56a7
comparison
equal deleted inserted replaced
2623:49533de4540b 2624:56f94936df1e
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from sat.core.i18n import _, D_ 20 from sat.core.i18n import _, D_
21 from sat.core.constants import Const as C 21 from sat.core.constants import Const as C
22 from sat.core.log import getLogger 22 from sat.core.log import getLogger
23
23 log = getLogger(__name__) 24 log = getLogger(__name__)
24 from twisted.words.xish import domish 25 from twisted.words.xish import domish
25 from twisted.internet import reactor 26 from twisted.internet import reactor
26 from twisted.words.protocols.jabber import jid 27 from twisted.words.protocols.jabber import jid
27 from twisted.internet import defer 28 from twisted.internet import defer
28 from sat.core import exceptions 29 from sat.core import exceptions
29 import os.path 30 import os.path
30 import copy 31 import copy
31 import time 32 import time
32 from os import unlink 33 from os import unlink
34
33 try: 35 try:
34 from mutagen.oggvorbis import OggVorbis, OggVorbisHeaderError 36 from mutagen.oggvorbis import OggVorbis, OggVorbisHeaderError
35 from mutagen.mp3 import MP3, HeaderNotFoundError 37 from mutagen.mp3 import MP3, HeaderNotFoundError
36 from mutagen.easyid3 import EasyID3 38 from mutagen.easyid3 import EasyID3
37 from mutagen.id3 import ID3NoHeaderError 39 from mutagen.id3 import ID3NoHeaderError
38 except ImportError: 40 except ImportError:
39 raise exceptions.MissingModule(u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen") 41 raise exceptions.MissingModule(
40 42 u"Missing module Mutagen, please download/install from https://bitbucket.org/lazka/mutagen"
41 43 )
42 NC_RADIOCOL = 'http://www.goffi.org/protocol/radiocol' 44
43 RADIOC_TAG = 'radiocol' 45
46 NC_RADIOCOL = "http://www.goffi.org/protocol/radiocol"
47 RADIOC_TAG = "radiocol"
44 48
45 PLUGIN_INFO = { 49 PLUGIN_INFO = {
46 C.PI_NAME: "Radio collective plugin", 50 C.PI_NAME: "Radio collective plugin",
47 C.PI_IMPORT_NAME: "Radiocol", 51 C.PI_IMPORT_NAME: "Radiocol",
48 C.PI_TYPE: "Exp", 52 C.PI_TYPE: "Exp",
49 C.PI_PROTOCOLS: [], 53 C.PI_PROTOCOLS: [],
50 C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249", "ROOM-GAME"], 54 C.PI_DEPENDENCIES: ["XEP-0045", "XEP-0249", "ROOM-GAME"],
51 C.PI_MAIN: "Radiocol", 55 C.PI_MAIN: "Radiocol",
52 C.PI_HANDLER: "yes", 56 C.PI_HANDLER: "yes",
53 C.PI_DESCRIPTION: _("""Implementation of radio collective""") 57 C.PI_DESCRIPTION: _("""Implementation of radio collective"""),
54 } 58 }
55 59
56 60
57 # Number of songs needed in the queue before we start playing 61 # Number of songs needed in the queue before we start playing
58 QUEUE_TO_START = 2 62 QUEUE_TO_START = 2
59 # Maximum number of songs in the queue (the song being currently played doesn't count) 63 # Maximum number of songs in the queue (the song being currently played doesn't count)
60 QUEUE_LIMIT = 2 64 QUEUE_LIMIT = 2
61 65
62 66
63 class Radiocol(object): 67 class Radiocol(object):
64
65 def inheritFromRoomGame(self, host): 68 def inheritFromRoomGame(self, host):
66 global RoomGame 69 global RoomGame
67 RoomGame = host.plugins["ROOM-GAME"].__class__ 70 RoomGame = host.plugins["ROOM-GAME"].__class__
68 self.__class__ = type(self.__class__.__name__, (self.__class__, RoomGame, object), {}) 71 self.__class__ = type(
72 self.__class__.__name__, (self.__class__, RoomGame, object), {}
73 )
69 74
70 def __init__(self, host): 75 def __init__(self, host):
71 log.info(_("Radio collective initialization")) 76 log.info(_("Radio collective initialization"))
72 self.inheritFromRoomGame(host) 77 self.inheritFromRoomGame(host)
73 RoomGame._init_(self, host, PLUGIN_INFO, (NC_RADIOCOL, RADIOC_TAG), 78 RoomGame._init_(
74 game_init={'queue': [], 'upload': True, 'playing': None, 'playing_time': 0, 'to_delete': {}}) 79 self,
80 host,
81 PLUGIN_INFO,
82 (NC_RADIOCOL, RADIOC_TAG),
83 game_init={
84 "queue": [],
85 "upload": True,
86 "playing": None,
87 "playing_time": 0,
88 "to_delete": {},
89 },
90 )
75 self.host = host 91 self.host = host
76 host.bridge.addMethod("radiocolLaunch", ".plugin", in_sign='asss', out_sign='', method=self._prepareRoom, async=True) 92 host.bridge.addMethod(
77 host.bridge.addMethod("radiocolCreate", ".plugin", in_sign='sass', out_sign='', method=self._createGame) 93 "radiocolLaunch",
78 host.bridge.addMethod("radiocolSongAdded", ".plugin", in_sign='sss', out_sign='', method=self._radiocolSongAdded, async=True) 94 ".plugin",
79 host.bridge.addSignal("radiocolPlayers", ".plugin", signature='ssass') # room_jid, referee, players, profile 95 in_sign="asss",
80 host.bridge.addSignal("radiocolStarted", ".plugin", signature='ssasais') # room_jid, referee, players, [QUEUE_TO_START, QUEUE_LIMIT], profile 96 out_sign="",
81 host.bridge.addSignal("radiocolSongRejected", ".plugin", signature='sss') # room_jid, reason, profile 97 method=self._prepareRoom,
82 host.bridge.addSignal("radiocolPreload", ".plugin", signature='ssssssss') # room_jid, timestamp, filename, title, artist, album, profile 98 async=True,
83 host.bridge.addSignal("radiocolPlay", ".plugin", signature='sss') # room_jid, filename, profile 99 )
84 host.bridge.addSignal("radiocolNoUpload", ".plugin", signature='ss') # room_jid, profile 100 host.bridge.addMethod(
85 host.bridge.addSignal("radiocolUploadOk", ".plugin", signature='ss') # room_jid, profile 101 "radiocolCreate",
102 ".plugin",
103 in_sign="sass",
104 out_sign="",
105 method=self._createGame,
106 )
107 host.bridge.addMethod(
108 "radiocolSongAdded",
109 ".plugin",
110 in_sign="sss",
111 out_sign="",
112 method=self._radiocolSongAdded,
113 async=True,
114 )
115 host.bridge.addSignal(
116 "radiocolPlayers", ".plugin", signature="ssass"
117 ) # room_jid, referee, players, profile
118 host.bridge.addSignal(
119 "radiocolStarted", ".plugin", signature="ssasais"
120 ) # room_jid, referee, players, [QUEUE_TO_START, QUEUE_LIMIT], profile
121 host.bridge.addSignal(
122 "radiocolSongRejected", ".plugin", signature="sss"
123 ) # room_jid, reason, profile
124 host.bridge.addSignal(
125 "radiocolPreload", ".plugin", signature="ssssssss"
126 ) # room_jid, timestamp, filename, title, artist, album, profile
127 host.bridge.addSignal(
128 "radiocolPlay", ".plugin", signature="sss"
129 ) # room_jid, filename, profile
130 host.bridge.addSignal(
131 "radiocolNoUpload", ".plugin", signature="ss"
132 ) # room_jid, profile
133 host.bridge.addSignal(
134 "radiocolUploadOk", ".plugin", signature="ss"
135 ) # room_jid, profile
86 136
87 def __create_preload_elt(self, sender, song_added_elt): 137 def __create_preload_elt(self, sender, song_added_elt):
88 preload_elt = copy.deepcopy(song_added_elt) 138 preload_elt = copy.deepcopy(song_added_elt)
89 preload_elt.name = 'preload' 139 preload_elt.name = "preload"
90 preload_elt['sender'] = sender 140 preload_elt["sender"] = sender
91 preload_elt['timestamp'] = str(time.time()) 141 preload_elt["timestamp"] = str(time.time())
92 # attributes filename, title, artist, album, length have been copied 142 # attributes filename, title, artist, album, length have been copied
93 # XXX: the frontend should know the temporary directory where file is put 143 # XXX: the frontend should know the temporary directory where file is put
94 return preload_elt 144 return preload_elt
95 145
96 def _radiocolSongAdded(self, referee_s, song_path, profile): 146 def _radiocolSongAdded(self, referee_s, song_path, profile):
106 # XXX: this is a Q&D way for the proof of concept. In the future, the song should 156 # XXX: this is a Q&D way for the proof of concept. In the future, the song should
107 # be streamed to the backend using XMPP file copy 157 # be streamed to the backend using XMPP file copy
108 # Here we cheat because we know we are on the same host, and we don't 158 # Here we cheat because we know we are on the same host, and we don't
109 # check data. Referee will have to parse the song himself to check it 159 # check data. Referee will have to parse the song himself to check it
110 try: 160 try:
111 if song_path.lower().endswith('.mp3'): 161 if song_path.lower().endswith(".mp3"):
112 actual_song = MP3(song_path) 162 actual_song = MP3(song_path)
113 try: 163 try:
114 song = EasyID3(song_path) 164 song = EasyID3(song_path)
115 165
116 class Info(object): 166 class Info(object):
117 def __init__(self, length): 167 def __init__(self, length):
118 self.length = length 168 self.length = length
169
119 song.info = Info(actual_song.info.length) 170 song.info = Info(actual_song.info.length)
120 except ID3NoHeaderError: 171 except ID3NoHeaderError:
121 song = actual_song 172 song = actual_song
122 else: 173 else:
123 song = OggVorbis(song_path) 174 song = OggVorbis(song_path)
124 except (OggVorbisHeaderError, HeaderNotFoundError): 175 except (OggVorbisHeaderError, HeaderNotFoundError):
125 # this file is not ogg vorbis nor mp3, we reject it 176 # this file is not ogg vorbis nor mp3, we reject it
126 self.deleteFile(song_path) # FIXME: same host trick (see note above) 177 self.deleteFile(song_path) # FIXME: same host trick (see note above)
127 return defer.fail(exceptions.DataError(D_("The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted."))) 178 return defer.fail(
128 179 exceptions.DataError(
129 attrs = {'filename': os.path.basename(song_path), 180 D_(
130 'title': song.get("title", ["Unknown"])[0], 181 "The uploaded file has been rejected, only Ogg Vorbis and MP3 songs are accepted."
131 'artist': song.get("artist", ["Unknown"])[0], 182 )
132 'album': song.get("album", ["Unknown"])[0], 183 )
133 'length': str(song.info.length) 184 )
134 } 185
135 radio_data = self.games[referee.userhostJID()] # FIXME: referee comes from Libervia's client side, it's unsecure 186 attrs = {
136 radio_data['to_delete'][attrs['filename']] = song_path # FIXME: works only because of the same host trick, see the note under the docstring 187 "filename": os.path.basename(song_path),
137 return self.send(referee, ('', 'song_added'), attrs, profile=profile) 188 "title": song.get("title", ["Unknown"])[0],
189 "artist": song.get("artist", ["Unknown"])[0],
190 "album": song.get("album", ["Unknown"])[0],
191 "length": str(song.info.length),
192 }
193 radio_data = self.games[
194 referee.userhostJID()
195 ] # FIXME: referee comes from Libervia's client side, it's unsecure
196 radio_data["to_delete"][
197 attrs["filename"]
198 ] = (
199 song_path
200 ) # FIXME: works only because of the same host trick, see the note under the docstring
201 return self.send(referee, ("", "song_added"), attrs, profile=profile)
138 202
139 def playNext(self, room_jid, profile): 203 def playNext(self, room_jid, profile):
140 """"Play next song in queue if exists, and put a timer 204 """"Play next song in queue if exists, and put a timer
141 which trigger after the song has been played to play next one""" 205 which trigger after the song has been played to play next one"""
142 # TODO: songs need to be erased once played or found invalids 206 # TODO: songs need to be erased once played or found invalids
143 # ==> unlink done the Q&D way with the same host trick (see above) 207 # ==> unlink done the Q&D way with the same host trick (see above)
144 radio_data = self.games[room_jid] 208 radio_data = self.games[room_jid]
145 if len(radio_data['players']) == 0: 209 if len(radio_data["players"]) == 0:
146 log.debug(_(u'No more participants in the radiocol: cleaning data')) 210 log.debug(_(u"No more participants in the radiocol: cleaning data"))
147 radio_data['queue'] = [] 211 radio_data["queue"] = []
148 for filename in radio_data['to_delete']: 212 for filename in radio_data["to_delete"]:
149 self.deleteFile(filename, radio_data) 213 self.deleteFile(filename, radio_data)
150 radio_data['to_delete'] = {} 214 radio_data["to_delete"] = {}
151 queue = radio_data['queue'] 215 queue = radio_data["queue"]
152 if not queue: 216 if not queue:
153 # nothing left to play, we need to wait for uploads 217 # nothing left to play, we need to wait for uploads
154 radio_data['playing'] = None 218 radio_data["playing"] = None
155 return 219 return
156 song = queue.pop(0) 220 song = queue.pop(0)
157 filename, length = song['filename'], float(song['length']) 221 filename, length = song["filename"], float(song["length"])
158 self.send(room_jid, ('', 'play'), {'filename': filename}, profile=profile) 222 self.send(room_jid, ("", "play"), {"filename": filename}, profile=profile)
159 radio_data['playing'] = song 223 radio_data["playing"] = song
160 radio_data['playing_time'] = time.time() 224 radio_data["playing_time"] = time.time()
161 225
162 if not radio_data['upload'] and len(queue) < QUEUE_LIMIT: 226 if not radio_data["upload"] and len(queue) < QUEUE_LIMIT:
163 # upload is blocked and we now have resources to get more, we reactivate it 227 # upload is blocked and we now have resources to get more, we reactivate it
164 self.send(room_jid, ('', 'upload_ok'), profile=profile) 228 self.send(room_jid, ("", "upload_ok"), profile=profile)
165 radio_data['upload'] = True 229 radio_data["upload"] = True
166 230
167 reactor.callLater(length, self.playNext, room_jid, profile) 231 reactor.callLater(length, self.playNext, room_jid, profile)
168 # we wait more than the song length to delete the file, to manage poorly reactive networks/clients 232 # we wait more than the song length to delete the file, to manage poorly reactive networks/clients
169 reactor.callLater(length + 90, self.deleteFile, filename, radio_data) # FIXME: same host trick (see above) 233 reactor.callLater(
234 length + 90, self.deleteFile, filename, radio_data
235 ) # FIXME: same host trick (see above)
170 236
171 def deleteFile(self, filename, radio_data=None): 237 def deleteFile(self, filename, radio_data=None):
172 """ 238 """
173 Delete a previously uploaded file. 239 Delete a previously uploaded file.
174 @param filename: filename to delete, or full filepath if radio_data is None 240 @param filename: filename to delete, or full filepath if radio_data is None
175 @param radio_data: current game data 241 @param radio_data: current game data
176 @return: True if the file has been deleted 242 @return: True if the file has been deleted
177 """ 243 """
178 if radio_data: 244 if radio_data:
179 try: 245 try:
180 file_to_delete = radio_data['to_delete'][filename] 246 file_to_delete = radio_data["to_delete"][filename]
181 except KeyError: 247 except KeyError:
182 log.error(_(u"INTERNAL ERROR: can't find full path of the song to delete")) 248 log.error(
249 _(u"INTERNAL ERROR: can't find full path of the song to delete")
250 )
183 return False 251 return False
184 else: 252 else:
185 file_to_delete = filename 253 file_to_delete = filename
186 try: 254 try:
187 unlink(file_to_delete) 255 unlink(file_to_delete)
188 except OSError: 256 except OSError:
189 log.error(_(u"INTERNAL ERROR: can't find %s on the file system" % file_to_delete)) 257 log.error(
258 _(u"INTERNAL ERROR: can't find %s on the file system" % file_to_delete)
259 )
190 return False 260 return False
191 return True 261 return True
192 262
193 def room_game_cmd(self, mess_elt, profile): 263 def room_game_cmd(self, mess_elt, profile):
194 from_jid = jid.JID(mess_elt['from']) 264 from_jid = jid.JID(mess_elt["from"])
195 room_jid = from_jid.userhostJID() 265 room_jid = from_jid.userhostJID()
196 nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile) 266 nick = self.host.plugins["XEP-0045"].getRoomNick(room_jid, profile)
197 267
198 radio_elt = mess_elt.firstChildElement() 268 radio_elt = mess_elt.firstChildElement()
199 radio_data = self.games[room_jid] 269 radio_data = self.games[room_jid]
200 if 'queue' in radio_data: 270 if "queue" in radio_data:
201 queue = radio_data['queue'] 271 queue = radio_data["queue"]
202 272
203 from_referee = self.isReferee(room_jid, from_jid.resource) 273 from_referee = self.isReferee(room_jid, from_jid.resource)
204 to_referee = self.isReferee(room_jid, jid.JID(mess_elt['to']).user) 274 to_referee = self.isReferee(room_jid, jid.JID(mess_elt["to"]).user)
205 is_player = self.isPlayer(room_jid, nick) 275 is_player = self.isPlayer(room_jid, nick)
206 for elt in radio_elt.elements(): 276 for elt in radio_elt.elements():
207 if not from_referee and not (to_referee and elt.name == 'song_added'): 277 if not from_referee and not (to_referee and elt.name == "song_added"):
208 continue # sender must be referee, expect when a song is submitted 278 continue # sender must be referee, expect when a song is submitted
209 if not is_player and (elt.name not in ('started', 'players')): 279 if not is_player and (elt.name not in ("started", "players")):
210 continue # user is in the room but not playing 280 continue # user is in the room but not playing
211 281
212 if elt.name in ('started', 'players'): # new game created and/or players list updated 282 if elt.name in (
283 "started",
284 "players",
285 ): # new game created and/or players list updated
213 players = [] 286 players = []
214 for player in elt.elements(): 287 for player in elt.elements():
215 players.append(unicode(player)) 288 players.append(unicode(player))
216 signal = self.host.bridge.radiocolStarted if elt.name == 'started' else self.host.bridge.radiocolPlayers 289 signal = (
217 signal(room_jid.userhost(), from_jid.full(), players, [QUEUE_TO_START, QUEUE_LIMIT], profile) 290 self.host.bridge.radiocolStarted
218 elif elt.name == 'preload': # a song is in queue and must be preloaded 291 if elt.name == "started"
219 self.host.bridge.radiocolPreload(room_jid.userhost(), elt['timestamp'], elt['filename'], elt['title'], elt['artist'], elt['album'], elt['sender'], profile) 292 else self.host.bridge.radiocolPlayers
220 elif elt.name == 'play': 293 )
221 self.host.bridge.radiocolPlay(room_jid.userhost(), elt['filename'], profile) 294 signal(
222 elif elt.name == 'song_rejected': # a song has been refused 295 room_jid.userhost(),
223 self.host.bridge.radiocolSongRejected(room_jid.userhost(), elt['reason'], profile) 296 from_jid.full(),
224 elif elt.name == 'no_upload': 297 players,
298 [QUEUE_TO_START, QUEUE_LIMIT],
299 profile,
300 )
301 elif elt.name == "preload": # a song is in queue and must be preloaded
302 self.host.bridge.radiocolPreload(
303 room_jid.userhost(),
304 elt["timestamp"],
305 elt["filename"],
306 elt["title"],
307 elt["artist"],
308 elt["album"],
309 elt["sender"],
310 profile,
311 )
312 elif elt.name == "play":
313 self.host.bridge.radiocolPlay(
314 room_jid.userhost(), elt["filename"], profile
315 )
316 elif elt.name == "song_rejected": # a song has been refused
317 self.host.bridge.radiocolSongRejected(
318 room_jid.userhost(), elt["reason"], profile
319 )
320 elif elt.name == "no_upload":
225 self.host.bridge.radiocolNoUpload(room_jid.userhost(), profile) 321 self.host.bridge.radiocolNoUpload(room_jid.userhost(), profile)
226 elif elt.name == 'upload_ok': 322 elif elt.name == "upload_ok":
227 self.host.bridge.radiocolUploadOk(room_jid.userhost(), profile) 323 self.host.bridge.radiocolUploadOk(room_jid.userhost(), profile)
228 elif elt.name == 'song_added': # a song has been added 324 elif elt.name == "song_added": # a song has been added
229 # FIXME: we are KISS for the proof of concept: every song is added, to a limit of 3 in queue. 325 # FIXME: we are KISS for the proof of concept: every song is added, to a limit of 3 in queue.
230 # Need to manage some sort of rules to allow peoples to send songs 326 # Need to manage some sort of rules to allow peoples to send songs
231 if len(queue) >= QUEUE_LIMIT: 327 if len(queue) >= QUEUE_LIMIT:
232 # there are already too many songs in queue, we reject this one 328 # there are already too many songs in queue, we reject this one
233 # FIXME: add an error code 329 # FIXME: add an error code
234 self.send(from_jid, ('', 'song_rejected'), {'reason': "Too many songs in queue"}, profile=profile) 330 self.send(
331 from_jid,
332 ("", "song_rejected"),
333 {"reason": "Too many songs in queue"},
334 profile=profile,
335 )
235 return 336 return
236 337
237 # The song is accepted and added in queue 338 # The song is accepted and added in queue
238 preload_elt = self.__create_preload_elt(from_jid.resource, elt) 339 preload_elt = self.__create_preload_elt(from_jid.resource, elt)
239 queue.append(preload_elt) 340 queue.append(preload_elt)
240 341
241 if len(queue) >= QUEUE_LIMIT: 342 if len(queue) >= QUEUE_LIMIT:
242 # We are at the limit, we refuse new upload until next play 343 # We are at the limit, we refuse new upload until next play
243 self.send(room_jid, ('', 'no_upload'), profile=profile) 344 self.send(room_jid, ("", "no_upload"), profile=profile)
244 radio_data['upload'] = False 345 radio_data["upload"] = False
245 346
246 self.send(room_jid, preload_elt, profile=profile) 347 self.send(room_jid, preload_elt, profile=profile)
247 if not radio_data['playing'] and len(queue) == QUEUE_TO_START: 348 if not radio_data["playing"] and len(queue) == QUEUE_TO_START:
248 # We have not started playing yet, and we have QUEUE_TO_START 349 # We have not started playing yet, and we have QUEUE_TO_START
249 # songs in queue. We can now start the party :) 350 # songs in queue. We can now start the party :)
250 self.playNext(room_jid, profile) 351 self.playNext(room_jid, profile)
251 else: 352 else:
252 log.error(_(u'Unmanaged game element: %s') % elt.name) 353 log.error(_(u"Unmanaged game element: %s") % elt.name)
253 354
254 def getSyncDataForPlayer(self, room_jid, nick): 355 def getSyncDataForPlayer(self, room_jid, nick):
255 game_data = self.games[room_jid] 356 game_data = self.games[room_jid]
256 elements = [] 357 elements = []
257 if game_data['playing']: 358 if game_data["playing"]:
258 preload = copy.deepcopy(game_data['playing']) 359 preload = copy.deepcopy(game_data["playing"])
259 current_time = game_data['playing_time'] + 1 if self.testing else time.time() 360 current_time = game_data["playing_time"] + 1 if self.testing else time.time()
260 preload['filename'] += '#t=%.2f' % (current_time - game_data['playing_time']) 361 preload["filename"] += "#t=%.2f" % (current_time - game_data["playing_time"])
261 elements.append(preload) 362 elements.append(preload)
262 play = domish.Element(('', 'play')) 363 play = domish.Element(("", "play"))
263 play['filename'] = preload['filename'] 364 play["filename"] = preload["filename"]
264 elements.append(play) 365 elements.append(play)
265 if len(game_data['queue']) > 0: 366 if len(game_data["queue"]) > 0:
266 elements.extend(copy.deepcopy(game_data['queue'])) 367 elements.extend(copy.deepcopy(game_data["queue"]))
267 if len(game_data['queue']) == QUEUE_LIMIT: 368 if len(game_data["queue"]) == QUEUE_LIMIT:
268 elements.append(domish.Element(('', 'no_upload'))) 369 elements.append(domish.Element(("", "no_upload")))
269 return elements 370 return elements