def load_from_id(klass, song_id, sid = None, all_categories = False): if sid is not None: d = db.c.fetch_row("SELECT * FROM r4_songs JOIN r4_song_sid USING (song_id) WHERE r4_songs.song_id = %s AND r4_song_sid.sid = %s", (song_id, sid)) else: d = db.c.fetch_row("SELECT * FROM r4_songs WHERE song_id = %s", (song_id,)) sid = d['song_origin_sid'] if not d: raise SongNonExistent try: s = klass() s.id = song_id s.sid = sid s.filename = d['song_filename'] s.verified = d['song_verified'] s.replay_gain = d['song_replay_gain'] s.data['sids'] = db.c.fetch_list("SELECT sid FROM r4_song_sid WHERE song_id = %s", (song_id,)) s.data['sid'] = sid s.data['rank'] = None s._assign_from_dict(d) if 'album_id' in d and d['album_id']: s.albums = [ Album.load_from_id_sid(d['album_id'], sid) ] s.artists = Artist.load_list_from_song_id(song_id) s.groups = SongGroup.load_list_from_song_id(song_id, sid, all_categories = all_categories) except Exception as e: log.exception("song", "Song ID %s failed to load, sid %s." % (song_id, sid), e) s.disable() raise return s
def load_from_id(klass, song_id, sid = None): if sid is not None: d = db.c.fetch_row("SELECT * FROM r4_songs JOIN r4_song_sid USING (song_id) WHERE r4_songs.song_id = %s AND r4_song_sid.sid = %s", (song_id, sid)) else: d = db.c.fetch_row("SELECT * FROM r4_songs WHERE song_id = %s", (song_id,)) if not d: raise SongNonExistent try: s = klass() s.id = song_id s.sid = sid s.filename = d['song_filename'] s.verified = d['song_verified'] s.replay_gain = d['song_replay_gain'] s.data['sids'] = db.c.fetch_list("SELECT sid FROM r4_song_sid WHERE song_id = %s", (song_id,)) s.data['sid'] = sid s.data['rank'] = None s._assign_from_dict(d) if 'album_id' in d and d['album_id']: if sid is not None: s.albums = [ Album.load_from_id_sid(d['album_id'], s.sid) ] else: s.albums = [ Album.load_from_id(d['album_id']) ] s.artists = Artist.load_list_from_song_id(song_id) s.groups = SongGroup.load_list_from_song_id(song_id) except Exception as e: log.exception("song", "Song ID %s failed to load, sid %s." % (song_id, sid), e) s.disable() raise return s
def _add_scan_error(filename, xception): global _scan_errors _scan_errors.insert(0, { "time": time.time(), "file": filename, "type": xception.__class__.__name__, "error": str(xception) }) log.exception("scan", "Error scanning %s" % filename, xception) if config.test_mode: raise Exception(_scan_errors[0])
async def post(self): if not config.has("sentry_dsn"): return try: sentry_host = config.get("sentry_host") envelope = self.request.body.decode("utf-8") piece = envelope.split("\n")[0] header = json.loads(piece) dsn = urllib.parse.urlparse(header.get("dsn")) if dsn.hostname != sentry_host: raise Exception(f"Invalid Sentry host: {dsn.hostname}") project_id = dsn.path.strip("/") if project_id != config.get("sentry_frontend_project_id"): raise Exception(f"Invalid Project ID: {project_id}") tunneled = HTTPRequest( url=f"https://{sentry_host}/api/{project_id}/envelope/", method="POST", body=envelope, ) http_client = AsyncHTTPClient() await http_client.fetch(tunneled) except Exception as e: log.exception("sentry", "Error tunneling Sentry", e) return {}
def _do_auth(self, message): try: if not "user_id" in message or not message['user_id']: self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="user_id") } }) if not isinstance(message['user_id'], numbers.Number): self.write_message({ "wserror": { "tl_key": "invalid_argument", "text": self.locale.translate("invalid_argument", argument="user_id") } }) if not "key" in message or not message['key']: self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="key") } }) self.user = User(message['user_id']) self.user.ip_address = self.request.remote_ip self.user.authorize(None, message['key']) if not self.user.authorized: self.write_message({ "wserror": { "tl_key": "auth_failed", "text": self.locale.translate("auth_failed") } }) self.close() return self.authorized = True self.uuid = str(uuid.uuid4()) global sessions sessions[self.sid].append(self) self.votes_by_key = self.request.remote_ip if self.user.is_anonymous() else self.user.id self.refresh_user() # no need to send the user's data to the user as that would have come with bootstrap # and will come with each synchronization of the schedule anyway self.write_message({ "wsok": True }) # since this will be the first action in any websocket interaction though, # it'd be a good time to send a station offline message. self._station_offline_check() except Exception as e: log.exception("websocket", "Exception during authentication.", e) self.close()
def _count(): global in_process log.debug("icecast_sync", "All responses came back for counting.") try: stations = {} for sid in config.station_ids: stations[sid] = 0 for handler, data in in_process.iteritems(): if isinstance(data, list): stations[handler.sid] += len(data) for sid, listener_count in stations.iteritems(): log.debug("icecast_sync", "%s has %s listeners." % (config.station_id_friendly[sid], listener_count)) db.c.update("INSERT INTO r4_listener_counts (sid, lc_guests) VALUES (%s, %s)", (sid, listener_count)) _cache_relay_status() # db.c.update("DELETE FROM r4_listener_counts WHERE lc_time <= %s", (current_time - config.get("trim_history_length"),)) in_process = {} except Exception as e: log.exception("icecast_sync", "Could not finish counting listeners.", e)
def _count(): global in_process log.debug("icecast_sync", "All responses came back for counting.") try: stations = {} for sid in config.station_ids: stations[sid] = 0 for handler, data in in_process.iteritems(): if isinstance(data, list): stations[handler.sid] += len(data) for sid, listener_count in stations.iteritems(): log.debug( "icecast_sync", "%s has %s listeners." % (config.station_id_friendly[sid], listener_count)) db.c.update( "INSERT INTO r4_listener_counts (sid, lc_guests) VALUES (%s, %s)", (sid, listener_count)) _cache_relay_status() # db.c.update("DELETE FROM r4_listener_counts WHERE lc_time <= %s", (current_time - config.get("trim_history_length"),)) in_process = {} except Exception as e: log.exception("icecast_sync", "Could not finish counting listeners.", e)
def _start(callback): global in_process if in_process: log.warn("icecast_sync", "Previous operation did not finish!") stream_names = {} for sid in config.station_ids: stream_names[sid] = config.get_station(sid, 'stream_filename') for relay, relay_info in config.get("relays").iteritems(): relay_base_url = "%s%s:%s/admin/listclients?mount=/" % (relay_info['protocol'], relay_info['ip_address'], relay_info['port']) for sid in relay_info['sids']: for ftype in ('.mp3', '.ogg'): try: handler = IcecastSyncCallback(relay, relay_info, ftype, sid, callback) in_process[handler] = False http_client = tornado.httpclient.HTTPClient() http_client.fetch(relay_base_url + stream_names[sid] + ftype, auth_username=relay_info['admin_username'], auth_password=relay_info['admin_password'], callback=handler.process) except Exception as e: log.exception("icecast_sync", "Could not sync %s %s.%s" % (relay, stream_names[sid], ftype), e) callback()
def _start(callback): global in_process if in_process: log.warn("icecast_sync", "Previous operation did not finish!") stream_names = {} for sid in config.station_ids: stream_names[sid] = config.get_station(sid, 'stream_filename') for relay, relay_info in config.get("relays").iteritems(): relay_base_url = "%s%s:%s/admin/listclients?mount=/" % ( relay_info['protocol'], relay_info['ip_address'], relay_info['port']) for sid in relay_info['sids']: for ftype in ('.mp3', '.ogg'): try: handler = IcecastSyncCallback(relay, relay_info, ftype, sid, callback) in_process[handler] = False http_client = tornado.httpclient.HTTPClient() http_client.fetch( relay_base_url + stream_names[sid] + ftype, auth_username=relay_info['admin_username'], auth_password=relay_info['admin_password'], callback=handler.process) except Exception as e: log.exception( "icecast_sync", "Could not sync %s %s.%s" % (relay, stream_names[sid], ftype), e) callback()
def keep_alive(self): for session in self.sessions + self.websockets: try: session.keep_alive() except Exception as e: session.rw_finish() log.exception("sync", "Session failed keepalive.", e)
def fill(self, target_song_length=None, skip_requests=False): # ONLY RUN _ADD_REQUESTS ONCE PER FILL if not skip_requests: self._add_requests() for i in range(len(self.songs), self._num_songs): try: if not target_song_length and len( self.songs) > 0 and 'length' in self.songs[0].data: target_song_length = self.songs[0].data['length'] log.debug( "elec_fill", "Second song in election, aligning to length %s" % target_song_length) song = self._fill_get_song(target_song_length) song.data['entry_votes'] = 0 song.data['entry_type'] = ElecSongTypes.normal song.data['elec_request_user_id'] = 0 song.data['elec_request_username'] = None self._check_song_for_conflict(song) self.add_song(song) except Exception as e: log.exception("elec_fill", "Song failed to fill in an election.", e) pass if len(self.songs) == 0: raise ElectionEmptyException
def get_producer_at_time(sid, at_time): to_ret = None local_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(at_time)) time_ahead = int((at_time - timestamp()) / 60) sched_id = db.c.fetch_var( "SELECT sched_id " "FROM r4_schedule " "WHERE sid = %s AND sched_start <= %s AND sched_end > %s " "ORDER BY sched_id DESC " "LIMIT 1", (sid, at_time + 20, at_time), ) try: to_ret = events.event.BaseProducer.load_producer_by_id(sched_id) if to_ret: to_ret.start_producer() except Exception as e: log.warn("get_producer", "Failed to obtain producer at time %s (%sm ahead)." % (local_time, time_ahead)) log.exception( "get_producer", "Failed to get an appropriate producer at time %s (%sm ahead)." % (local_time, time_ahead), e, ) if not to_ret: log.debug( "get_producer", "No producer at time %s (%sm ahead), defaulting to election." % (local_time, time_ahead) ) return election.ElectionProducer(sid) if not to_ret.has_next_event(): log.warn("get_producer", "Producer ID %s (type %s, %s) has no events." % (to_ret.id, to_ret.type, to_ret.name)) return election.ElectionProducer(sid) return to_ret
def get_producer_at_time(sid, at_time): to_ret = None local_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(at_time)) time_ahead = int((at_time - timestamp()) / 60) sched_id = db.c.fetch_var( "SELECT sched_id " "FROM r4_schedule " "WHERE sid = %s AND sched_start <= %s AND sched_end > %s " "ORDER BY sched_id DESC " "LIMIT 1", (sid, at_time + 20, at_time)) try: to_ret = events.event.BaseProducer.load_producer_by_id(sched_id) if to_ret: to_ret.start_producer() except Exception as e: log.warn( "get_producer", "Failed to obtain producer at time %s (%sm ahead)." % (local_time, time_ahead)) log.exception( "get_producer", "Failed to get an appropriate producer at time %s (%sm ahead)." % (local_time, time_ahead), e) if not to_ret: log.debug( "get_producer", "No producer at time %s (%sm ahead), defaulting to election." % (local_time, time_ahead)) return election.ElectionProducer(sid) if not to_ret.has_next_event(): log.warn( "get_producer", "Producer ID %s (type %s, %s) has no events." % (to_ret.id, to_ret.type, to_ret.name)) return election.ElectionProducer(sid) return to_ret
def update(self): handler = APIHandler(websocket=True) handler.locale = self.locale handler.request = FakeRequestObject({}, self.request.cookies) handler.sid = self.sid handler.user = self.user handler.return_name = "sync_result" try: startclock = timestamp() handler.prepare_standalone() if not cache.get_station(self.sid, "backend_ok"): raise APIException("station_offline") self.refresh_user() api_requests.info.attach_info_to_request(handler, live_voting=True) if self.user.is_dj(): api_requests.info.attach_dj_info_to_request(handler) handler.append("user", self.user.to_private_dict()) handler.append("api_info", { "exectime": timestamp() - startclock, "time": round(timestamp()) }) except Exception as e: if handler: handler.write_error(500, exc_info=sys.exc_info(), no_finish=True) log.exception("websocket", "Exception during update.", e) finally: if handler: self.write_message(handler._output) #pylint: disable=W0212
def fill(self, target_song_length=None, skip_requests=False): # ONLY RUN _ADD_REQUESTS ONCE PER FILL if not skip_requests: self._add_requests() for i in range(len(self.songs), self._num_songs): #pylint: disable=W0612 try: if not target_song_length and len( self.songs) > 0 and 'length' in self.songs[0].data: target_song_length = self.songs[0].data['length'] log.debug( "elec_fill", "Second song in election, aligning to length %s" % target_song_length) song = self._fill_get_song(target_song_length) song.data['entry_votes'] = 0 song.data['entry_type'] = ElecSongTypes.normal song.data['elec_request_user_id'] = 0 song.data['elec_request_username'] = None self._check_song_for_conflict(song) self.add_song(song) except Exception as e: log.exception("elec_fill", "Song failed to fill in an election.", e) if len(self.songs) == 0: raise ElectionEmptyException for song in self.songs: if 'elec_request_user_id' in song.data and song.data[ 'elec_request_user_id']: log.debug( "elec_fill", "Putting user %s back in line after request fulfillment." % song.data['elec_request_username']) u = User(song.data['elec_request_user_id']) u.put_in_request_line(u.get_tuned_in_sid()) request.update_line(self.sid)
def load_translations(): global master global translations global locale_names_json master_file = open(os.path.join(os.path.dirname(__file__), "../lang/en_MASTER.json")) master = json.load(master_file) master_file.close() locale_names = {} for root, subdir, files in os.walk(os.path.join(os.path.dirname(__file__), "../lang")): #pylint: disable=W0612 for filename in files: if filename == "en_MASTER.json": continue if not filename.endswith(".json"): continue try: f = codecs.open(os.path.join(os.path.dirname(__file__), "../lang/", filename), "r", encoding="utf-8") translations[filename[:-5]] = RainwaveLocale(filename[:-5], master, json.load(f)) f.close() locale_names[filename[:-5]] = translations[filename[:-5]].dict['language_name'] except Exception as e: log.exception("locale", "%s translation did not load." % filename[:-5], e) locale_names_json = tornado.escape.json_encode(locale_names)
def _add_scan_error(filename, xception, full_exc=None): scan_errors = [] try: scan_errors = cache.get("backend_scan_errors") except: pass if not scan_errors: scan_errors = [] eo = { "time": int(timestamp()), "file": filename, "type": xception.__class__.__name__, "error": str(xception), "traceback": "" } if not isinstance(xception, PassableScanError) and not isinstance( xception, IOError) and not isinstance(xception, OSError): if full_exc: eo['traceback'] = traceback.format_exception(*full_exc) #pylint: disable=W0142 log.exception("scan", "Error scanning %s" % filename, full_exc) else: eo['traceback'] = traceback.format_exception(*sys.exc_info()) log.exception("scan", "Error scanning %s" % filename, sys.exc_info()) else: log.warn("scan", "Warning scanning %s: %s" % (filename, xception.message)) scan_errors.insert(0, eo) if len(scan_errors) > 100: scan_errors = scan_errors[0:100] cache.set("backend_scan_errors", scan_errors)
def execute(self, query, params=None): if self.print_next: self.print_next = False if params: print self._convert_pg_query(query, True) % params else: print self._convert_pg_query(query, True) query = self._convert_pg_query(query) # If the query can't be done or properly to SQLite, # silently drop it. This is mostly for table creation, things like foreign keys. if not query: log.critical("sqlite", "Query has not been made SQLite friendly: %s" % query) raise Exception("Query has not been made SQLite friendly: %s" % query) try: if params: self.cur.execute(query, params) else: self.cur.execute(query) except Exception as e: log.critical("sqlite", query) log.critical("sqlite", repr(params)) log.exception("sqlite", "Failed query.", e) raise self.rowcount = self.cur.rowcount self.con.commit()
def _do_user_update(self, session, updated_by_ip): # clear() might wipe out the timeouts for a bigger update (that includes user update anyway!) # don't bother updating again if that's already happened if not session in self.throttled: return del (self.throttled[session]) try: potential_mixup_warn = updated_by_ip and not session.user.is_anonymous( ) and not session.user.is_tunedin() session.refresh_user() if potential_mixup_warn and not session.user.is_tunedin(): log.debug( "sync_update_ip", "Warning logged in user of potential M3U mixup at IP %s" % session.request.remote_ip) session.login_mixup_warn() else: session.update_user() except Exception as e: log.exception("sync", "Session failed to be updated during update_user.", e) try: session.rw_finish() except Exception: log.exception("sync", "Session failed finish() during update_user.", e)
def monitor(): _common_init() pid = os.getpid() pid_file = open("%s/scanner.pid" % config.get_directory("pid_dir"), 'w') pid_file.write(str(pid)) pid_file.close() observers = [] for directory, sids in config.get("song_dirs").iteritems(): observer = watchdog.observers.Observer() observer.schedule(FileEventHandler(directory, sids), directory, recursive=True) observer.start() log.info("scan", "Observing %s with sids %s" % (directory, repr(sids))) observers.append(observer) try: while True: time.sleep(60) _process_album_art_queue() except Exception as e: log.exception("scan", "Exception leaked to top monitoring function.", e) for observer in observers: observer.stop() for observer in observers: observer.join()
def keep_alive(self): for session in self.sessions: try: session.keep_alive() except Exception as e: self.remove(session) log.exception("sync", "Session failed keepalive.", e)
def _do_ip_update(self, ip_address): if not ip_address in self.ip_update_timers or not self.ip_update_timers[ ip_address]: return del self.ip_update_timers[ip_address] for session in self.find_ip(ip_address): try: if session.user.is_anonymous(): session.update_user() log.debug("sync_update_ip", "Updated IP %s" % session.request.remote_ip) else: log.debug( "sync_update_ip", "Warning logged in user of potential mixup at IP %s" % session.request.remote_ip) session.anon_registered_mixup_warn() except Exception as e: try: session.finish() except: pass self.remove(session) log.exception( "sync", "Session failed to be updated during update_user.", e) self.clean()
def load_translations(): global master global translations global locale_names global locale_names_json master_file = open(os.path.join(os.path.dirname(__file__), "../lang/en_MASTER.json")) master = json.load(master_file) master_file.close() locale_names = {} for root, subdir, files in os.walk(os.path.join(os.path.dirname(__file__), "../lang")): #pylint: disable=W0612 for filename in files: if filename == "en_MASTER.json": continue if not filename.endswith(".json"): continue try: f = codecs.open(os.path.join(os.path.dirname(__file__), "../lang/", filename), "r", encoding="utf-8") translations[filename[:-5]] = RainwaveLocale(filename[:-5], master, json.load(f)) f.close() locale_names[filename[:-5]] = translations[filename[:-5]].dict['language_name_short'] except Exception as e: log.exception("locale", "%s translation did not load." % filename[:-5], e) locale_names_json = tornado.escape.json_encode(locale_names)
def load_from_id(klass, song_id, sid = None): if sid: d = db.c.fetch_row("SELECT * FROM r4_songs JOIN r4_song_sid USING (song_id) WHERE r4_songs.song_id = %s AND r4_song_sid.sid = %s", (song_id, sid)) else: d = db.c.fetch_row("SELECT * FROM r4_songs WHERE song_id = %s", (song_id,)) if not d: raise SongNonExistent try: s = klass() s.id = song_id s.sid = sid s.filename = d['song_filename'] s.verified = d['song_verified'] s.replay_gain = d['song_replay_gain'] s.data['sids'] = db.c.fetch_list("SELECT sid FROM r4_song_sid WHERE song_id = %s", (song_id,)) s.data['sid'] = sid s.data['rank'] = None s._assign_from_dict(d) if 'album_id' in d and d['album_id']: if sid: s.albums = [ Album.load_from_id_sid(d['album_id'], s.sid) ] else: s.albums = [ Album.load_from_id(d['album_id']) ] s.artists = Artist.load_list_from_song_id(song_id) s.groups = SongGroup.load_list_from_song_id(song_id) except Exception as e: log.exception("song", "Song failed to load.", e) db.c.update("UPDATE r4_songs SET song_verified = FALSE WHERE song_id = song_id") raise return s
def execute(self, query, params = None): if self.print_next: self.print_next = False if params: print self._convert_pg_query(query, True) % params else: print self._convert_pg_query(query, True) query = self._convert_pg_query(query) # If the query can't be done or properly to SQLite, # silently drop it. This is mostly for table creation, things like foreign keys. if not query: log.critical("sqlite", "Query has not been made SQLite friendly: %s" % query) raise Exception("Query has not been made SQLite friendly: %s" % query) try: if params: self.cur.execute(query, params) else: self.cur.execute(query) except Exception as e: log.critical("sqlite", query) log.critical("sqlite", repr(params)) log.exception("sqlite", "Failed query.", e) raise self.rowcount = self.cur.rowcount self.con.commit()
def fill(self, target_song_length = None, skip_requests = False): # ONLY RUN _ADD_REQUESTS ONCE PER FILL if not skip_requests: self._add_requests() for i in range(len(self.songs), self._num_songs): #pylint: disable=W0612 try: if not target_song_length and len(self.songs) > 0 and 'length' in self.songs[0].data: target_song_length = self.songs[0].data['length'] log.debug("elec_fill", "Second song in election, aligning to length %s" % target_song_length) song = self._fill_get_song(target_song_length) song.data['entry_votes'] = 0 song.data['entry_type'] = ElecSongTypes.normal song.data['elec_request_user_id'] = 0 song.data['elec_request_username'] = None self._check_song_for_conflict(song) self.add_song(song) except Exception as e: log.exception("elec_fill", "Song failed to fill in an election.", e) if len(self.songs) == 0: raise ElectionEmptyException for song in self.songs: if 'elec_request_user_id' in song.data and song.data['elec_request_user_id']: log.debug("elec_fill", "Putting user %s back in line after request fulfillment." % song.data['elec_request_username']) u = User(song.data['elec_request_user_id']) u.put_in_request_line(u.get_tuned_in_sid()) request.update_line(self.sid)
def _on_zmq(messages): global votes_by global last_vote_by for message in messages: try: message = json.loads(message) except Exception as e: log.exception("zeromq", "Error decoding ZeroMQ message.", e) return if not "action" in message or not message["action"]: log.critical("zeromq", "No action received from ZeroMQ.") try: if message["action"] == "result_sync": sessions[message["sid"]].send_to_user( message["user_id"], message["uuid_exclusion"], message["data"] ) elif message["action"] == "live_voting": sessions[message["sid"]].send_to_all( message["uuid_exclusion"], message["data"] ) delay_live_vote_removal(message["sid"]) elif message["action"] == "delayed_live_voting": if not delayed_live_vote_timers[message["sid"]]: delay_live_vote(message) delayed_live_vote[message["sid"]] = message elif message["action"] == "update_all": delay_live_vote_removal(message["sid"]) rainwave.playlist.update_num_songs() rainwave.playlist.prepare_cooldown_algorithm(message["sid"]) cache.update_local_cache_for_sid(message["sid"]) sessions[message["sid"]].update_all(message["sid"]) votes_by = {} last_vote_by = {} elif message["action"] == "update_ip": for sid in sessions: sessions[sid].update_ip_address(message["ip"]) elif message["action"] == "update_listen_key": for sid in sessions: sessions[sid].update_listen_key(message["listen_key"]) elif message["action"] == "update_user": for sid in sessions: sessions[sid].update_user(message["user_id"]) elif message["action"] == "update_dj": sessions[message["sid"]].update_dj() elif message["action"] == "ping": log.debug("zeromq", "Pong") elif message["action"] == "vote_by": votes_by[message["by"]] = ( votes_by[message["by"]] + 1 if message["by"] in votes_by else 1 ) last_vote_by[message["by"]] = timestamp() except Exception as e: log.exception( "zeromq", "Error handling Zero MQ action '%s'" % message["action"], e ) return
def _add_scan_error(filename, xception): global _scan_errors, _raise_scan_errors _scan_errors.insert(0, { "time": int(time.time()), "file": filename, "type": xception.__class__.__name__, "error": str(xception) }) _save_scan_errors() log.exception("scan", "Error scanning %s" % filename, xception) if config.test_mode or _raise_scan_errors: raise
def write_error(self, status_code, **kwargs): self.failed = True if kwargs.has_key("exc_info"): exc = kwargs['exc_info'][1] if isinstance(exc, APIException): exc.localize(self.locale) self.set_header("icecast-auth-message", exc.reason) log.debug("ldetect", "Relay command failed: %s" % exc.reason) log.exception("ldetect", "Exception encountered handling relay command.", exc) super(IcecastHandler, self).finish()
def _add_scan_error(filename, xception): scan_errors = cache.get("backend_scan_errors") if not scan_errors: scan_errors = [] scan_errors.insert(0, { "time": int(timestamp()), "file": filename, "type": xception.__class__.__name__, "error": str(xception) }) if len(scan_errors) > 100: scan_errors = scan_errors[0:100] cache.set("backend_scan_errors", scan_errors) log.exception("scan", "Error scanning %s" % filename, xception)
def update_dj(self): for session in self.websockets: if session.user.is_dj(): try: session.update_dj_only() log.debug("sync_update_dj", "Updated user %s session." % session.user.id) except Exception as e: try: session.rw_finish() except: pass log.exception("sync_update_dj", "Session failed to be updated during update_dj.", e)
def write_message(self, obj, *args, **kwargs): message = json.dumps(obj) try: super(WSHandler, self).write_message(message, *args, **kwargs) except tornado.websocket.WebSocketClosedError: self.on_close() except tornado.websocket.WebSocketError as e: log.exception("websocket", "WebSocket Error", e) try: self.close() except Exception: self.on_close()
def _add_scan_error(filename, xception): global _scan_errors _scan_errors.insert( 0, { "time": int(time.time()), "file": filename, "type": xception.__class__.__name__, "error": str(xception) }) _save_scan_errors() log.exception("scan", "Error scanning %s" % filename, xception)
def get(self): #pylint: disable=E0202,W0221 ph = self._output[self.return_name] self.write(self.render_string("bare_header.html", title="%s" % ph['name'])) self.write("<script>\nwindow.top.refresh_all_screens = false;\n</script>") self.write("<h2>%s</h2>" % ph['name']) self.write("<span>Times from the server:</span><br>") self.write("<div style='font-family: monospace;'>%s</div>" % get_ph_formatted_time(ph['start'], ph['end'], 'US/Eastern')) self.write("<div style='font-family: monospace;'>%s</div>" % get_ph_formatted_time(ph['start'], ph['end'], 'US/Pacific')) self.write("<div style='font-family: monospace;'>%s</div>" % get_ph_formatted_time(ph['start'], ph['end'], 'Europe/London')) self.write("<div style='font-family: monospace;'>%s</div>" % get_ph_formatted_time(ph['start'], ph['end'], 'Asia/Tokyo')) total_len = 0 for i, song in enumerate(ph['songs']): total_len += song['length'] self.write("<br><div>Total length of songs: <b>%d:%02d</b></div>" % (int(total_len / 3600), (total_len / 60) % 60)) self.write("<br><span>Change time. Use YOUR timezone.</span><br>") index.write_html_time_form(self, "power_hour", ph['start']) self.write("<br><button onclick=\"window.top.call_api('admin/change_producer_start_time', ") self.write("{ 'utc_time': document.getElementById('power_hour_timestamp').value, 'sched_id': %s });\"" % ph['id']) self.write(">Change Time</button></div><hr>") self.write("<button onclick=\"window.top.call_api('admin/delete_producer', { 'sched_id': %s });\">Delete This Power Hour</button><hr>" % ph['id']) self.write("Name: <input type='text' id='new_ph_name' value='%s'><br>" % ph['name']) self.write("<button onclick=\"window.top.call_api('admin/change_producer_name', { 'sched_id': %s, 'name': document.getElementById('new_ph_name').value });\">Change Name</button><hr>" % ph['id']) self.write("URL: <input type='text' id='new_ph_url' value='%s'><br>" % (ph['url'] or "")) self.write("<button onclick=\"window.top.call_api('admin/change_producer_url', { 'sched_id': %s, 'url': document.getElementById('new_ph_url').value });\">Change URL</button><hr>" % ph['id']) self.write("<button onclick=\"window.top.call_api('admin/shuffle_power_hour', { 'sched_id': %s });\">Shuffle the Song Order</button><hr>\n\n" % ph['id']) try: self.write("<ol>") for song in ph['songs']: self.write("<li><div>%s" % song['title']) if song['one_up_used']: self.write(" <b>(PLAYED)</b>") elif song['one_up_queued']: self.write(" (queued)") self.write("</div><div>%s</div>\n" % song['albums'][0]['name']) self.write("<div>") self.write("<a onclick=\"window.top.call_api('admin/remove_from_power_hour', { 'one_up_id': %s });\">Delete</a> - " % song['one_up_id']) self.write("<a onclick=\"window.top.call_api('admin/move_up_in_power_hour', { 'one_up_id': %s });\">Move Up</a>" % song['one_up_id']) self.write("</div></li>\n") self.write("</ol>\n") self.write("<script>window.top.current_sched_id = %s;</script>\n\n" % ph['id']) except Exception as e: self.write("</ol>") self.write("<div>ERROR DISPLAYING SONG LIST. Something is wrong. Consult Rob. Do not play this Power Hour.</div>") log.exception("admin", "Could not display song list.", e) self.write(self.render_string("basic_footer.html"))
def _on_zmq(messages): global votes_by global last_vote_by for message in messages: try: message = json.loads(message) except Exception as e: log.exception("zeromq", "Error decoding ZeroMQ message.", e) return if not 'action' in message or not message['action']: log.critical("zeromq", "No action received from ZeroMQ.") try: if message['action'] == "result_sync": sessions[message['sid']].send_to_user(message['user_id'], message['uuid_exclusion'], message['data']) elif message['action'] == "live_voting": sessions[message['sid']].send_to_all(message['uuid_exclusion'], message['data']) delay_live_vote_removal(message['sid']) elif message['action'] == "delayed_live_voting": if not delayed_live_vote_timers[message['sid']]: delay_live_vote(message) delayed_live_vote[message['sid']] = message elif message['action'] == "update_all": delay_live_vote_removal(message['sid']) rainwave.playlist.update_num_songs() rainwave.playlist.prepare_cooldown_algorithm(message['sid']) cache.update_local_cache_for_sid(message['sid']) sessions[message['sid']].update_all(message['sid']) votes_by = {} last_vote_by = {} elif message['action'] == "update_ip": for sid in sessions: sessions[sid].update_ip_address(message['ip']) elif message['action'] == "update_listen_key": for sid in sessions: sessions[sid].update_listen_key(message['listen_key']) elif message['action'] == "update_user": for sid in sessions: sessions[sid].update_user(message['user_id']) elif message['action'] == "update_dj": sessions[message['sid']].update_dj() elif message['action'] == "ping": log.debug("zeromq", "Pong") elif message['action'] == "vote_by": votes_by[message['by']] = votes_by[message['by']] + 1 if message['by'] in votes_by else 1 last_vote_by[message['by']] = timestamp() except Exception as e: log.exception("zeromq", "Error handling Zero MQ action '%s'" % message['action'], e) return
def save_preferences(self, ip_addr, prefs_json_string): if not config.get("store_prefs") or not prefs_json_string: return try: prefs_json_string = parse.unquote(prefs_json_string) if self.id > 1: if not db.c.fetch_var( "SELECT COUNT(*) FROM r4_pref_storage WHERE user_id = %s", (self.id, ), ): db.c.update( "INSERT INTO r4_pref_storage (user_id, prefs) VALUES (%s, %s::jsonb)", (self.id, prefs_json_string), ) else: db.c.update( "UPDATE r4_pref_storage SET prefs = %s::jsonb WHERE user_id = %s", (prefs_json_string, self.id), ) else: if not db.c.fetch_var( "SELECT COUNT(*) FROM r4_pref_storage WHERE ip_address = %s AND user_id = %s", (ip_addr, self.id), ): db.c.update( "INSERT INTO r4_pref_storage (user_id, ip_address, prefs) VALUES (%s, %s, %s::jsonb)", (self.id, ip_addr, prefs_json_string), ) else: db.c.update( "UPDATE r4_pref_storage SET prefs = %s::jsonb WHERE ip_address = %s AND user_id = %s", (prefs_json_string, ip_addr, self.id), ) except Exception as e: if "name" in self.data: log.exception( "store_prefs", "Could not store user preferences for %s (ID %s)" % (self.data["name"], self.id), e, ) else: log.exception( "store_prefs", "Could not store user preferences for anonymous user from IP %s" % ip_addr, e, )
def get_producer_at_time(sid, at_time): to_ret = None sched_id = db.c.fetch_var( "SELECT sched_id " "FROM r4_schedule " "WHERE sid = %s AND sched_start <= %s AND sched_end > %s " "ORDER BY sched_id DESC " "LIMIT 1", (sid, at_time + 20, at_time)) try: to_ret = events.event.BaseProducer.load_producer_by_id(sched_id) except Exception as e: log.warn("get_producer", "Failed to obtain producer.") log.exception("get_producer", "Failed to get an appropriate producer.", e) if not to_ret: return election.ElectionProducer(sid) return to_ret
def execute(self, *args, **kwargs): if self.disconnected: raise DatabaseDisconnectedError try: return super().execute(*args, **kwargs) except connection_errors as e: if self.auto_retry: log.exception("psycopg", "Psycopg exception", e) close() connect(auto_retry=self.auto_retry) global c return c.execute(*args, **kwargs) else: raise
def _add_scan_error(filename, xception, full_exc=None): scan_errors = cache.get("backend_scan_errors") if not scan_errors: scan_errors = [] eo = { "time": int(timestamp()), "file": filename, "type": xception.__class__.__name__, "error": str(xception), "traceback": "" } if not isinstance(xception, PassableScanError) and not isinstance(xception, IOError) and not isinstance(xception, OSError): if full_exc: eo['traceback'] = traceback.format_exception(*full_exc) #pylint: disable=W0142 else: eo['traceback'] = traceback.format_exception(*sys.exc_info()) scan_errors.insert(0, eo) if len(scan_errors) > 100: scan_errors = scan_errors[0:100] cache.set("backend_scan_errors", scan_errors) log.exception("scan", "Error scanning %s" % filename, xception)
def update_all(self, sid): session_count = 0 session_failed_count = 0 for session in self.sessions + self.websockets: try: session.update() session_count += 1 except Exception as e: try: session.rw_finish() except: pass session_failed_count += 1 log.exception("sync_update_all", "Failed to update session.", e) log.debug("sync_update_all", "Updated %s sessions (%s failed) for sid %s." % (session_count, session_failed_count, sid)) self.clear()
def _do_user_update(self, user_id): # clear() might wipe out the timeouts - let's make sure we don't waste resources # doing unnecessary updates if not user_id in self.user_update_timers or not self.user_update_timers[user_id]: return del self.user_update_timers[user_id] for session in self.find_user(user_id): try: session.update_user() log.debug("sync_update_user", "Updated user %s session." % session.user.id) except Exception as e: try: session.finish() except: pass log.exception("sync_update_user", "Session failed to be updated during update_user.", e) self.clean()
def connect(auto_retry=True, retry_only_this_time=False): global connection global c if connection and c and not c.disconnected: return True name = config.get("db_name") host = config.get("db_host") port = config.get("db_port") user = config.get("db_user") password = config.get("db_password") psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY) base_connstr = "sslmode=disable " if host: base_connstr += "host=%s " % host if port: base_connstr += "port=%s " % port if user: base_connstr += "user=%s " % user if password: base_connstr += "password=%s " % password connected = False while not connected: try: connection = psycopg2.connect(base_connstr + ("dbname=%s" % name), connect_timeout=1) connection.set_isolation_level( psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) connection.autocommit = True c = connection.cursor(cursor_factory=PostgresCursor) c.auto_retry = auto_retry connected = True except connection_errors as e: log.exception("psycopg", "Psycopg2 exception", e) if auto_retry or retry_only_this_time: time.sleep(1) else: raise return True
def fill(self, target_song_length = None, skip_requests = False): # ONLY RUN _ADD_REQUESTS ONCE PER FILL if not skip_requests: self._add_requests() for i in range(len(self.songs), self._num_songs): try: if not target_song_length and len(self.songs) > 0 and 'length' in self.songs[0].data: target_song_length = self.songs[0].data['length'] log.debug("elec_fill", "Second song in election, aligning to length %s" % target_song_length) song = self._fill_get_song(target_song_length) song.data['entry_votes'] = 0 song.data['entry_type'] = ElecSongTypes.normal song.data['elec_request_user_id'] = 0 song.data['elec_request_username'] = None self._check_song_for_conflict(song) self.add_song(song) except Exception as e: log.exception("elec_fill", "Song failed to fill in an election.", e) pass if len(self.songs) == 0: raise ElectionEmptyException
def _do_ip_update(self, ip_address): if not ip_address in self.ip_update_timers or not self.ip_update_timers[ip_address]: return del self.ip_update_timers[ip_address] for session in self.find_ip(ip_address): try: if session.user.is_anonymous(): session.update_user() log.debug("sync_update_ip", "Updated IP %s" % session.request.remote_ip) else: log.debug("sync_update_ip", "Warning logged in user of potential mixup at IP %s" % session.request.remote_ip) session.anon_registered_mixup_warn() except Exception as e: try: session.finish() except: pass self.remove(session) log.exception("sync", "Session failed to be updated during update_user.", e) self.clean()
def _do_user_update(self, session, updated_by_ip): # clear() might wipe out the timeouts for a bigger update (that includes user update anyway!) # don't bother updating again if that's already happened if not session in self.throttled: return del(self.throttled[session]) try: potential_mixup_warn = updated_by_ip and not session.user.is_anonymous() and not session.user.is_tunedin() session.refresh_user() if potential_mixup_warn and not session.user.is_tunedin(): log.debug("sync_update_ip", "Warning logged in user of potential M3U mixup at IP %s" % session.request.remote_ip) session.login_mixup_warn() else: session.update_user() except Exception as e: log.exception("sync", "Session failed to be updated during update_user.", e) try: session.rw_finish() except Exception: log.exception("sync", "Session failed finish() during update_user.", e)
def save_preferences(self, ip_addr, prefs_json_string): if not config.get("store_prefs") or not prefs_json_string: return if not db.c.is_postgres: return try: prefs_json_string = urllib2.unquote(prefs_json_string) if self.id > 1: if not db.c.fetch_var("SELECT COUNT(*) FROM r4_pref_storage WHERE user_id = %s", (self.id,)): db.c.update("INSERT INTO r4_pref_storage (user_id, prefs) VALUES (%s, %s::jsonb)", (self.id, prefs_json_string)) else: db.c.update("UPDATE r4_pref_storage SET prefs = %s::jsonb WHERE user_id = %s", (prefs_json_string, self.id)) else: if not db.c.fetch_var("SELECT COUNT(*) FROM r4_pref_storage WHERE ip_address = %s AND user_id = %s", (ip_addr, self.id)): db.c.update("INSERT INTO r4_pref_storage (user_id, ip_address, prefs) VALUES (%s, %s, %s::jsonb)", (self.id, ip_addr, prefs_json_string)) else: db.c.update("UPDATE r4_pref_storage SET prefs = %s::jsonb WHERE ip_address = %s AND user_id = %s", (prefs_json_string, ip_addr, self.id)) except Exception as e: if 'username' in self.data: log.exception("store_prefs", "Could not store user preferences for %s (ID %s)" % (self.data['username'], self.id), e) else: log.exception("store_prefs", "Could not store user preferences for anonymous user from IP %s" % ip_addr, e)
def post_process(sid): try: db.c.start_transaction() start_time = timestamp() playlist.prepare_cooldown_algorithm(sid) rainwave.playlist_objects.album.clear_updated_albums(sid) log.debug("post", "Playlist prepare time: %.6f" % (timestamp() - start_time,)) start_time = timestamp() current[sid].finish() for sched_id in db.c.fetch_list( "SELECT sched_id FROM r4_schedule WHERE sched_end < %s AND sched_used = FALSE", (timestamp(),) ): t_evt = events.event.BaseProducer.load_producer_by_id(sched_id) t_evt.finish() log.debug("post", "Current finish time: %.6f" % (timestamp() - start_time,)) start_time = timestamp() last_song = current[sid].get_song() if last_song: db.c.update("INSERT INTO r4_song_history (sid, song_id) VALUES (%s, %s)", (sid, last_song.id)) log.debug("post", "Last song insertion time: %s" % (timestamp() - start_time,)) start_time = timestamp() history[sid].insert(0, current[sid]) while len(history[sid]) > 5: history[sid].pop() log.debug("post", "History management: %.6f" % (timestamp() - start_time,)) start_time = timestamp() current[sid] = upnext[sid].pop(0) current[sid].start_event() log.debug("advance", "Current management: %.6f" % (timestamp() - start_time,)) start_time = timestamp() playlist.warm_cooled_songs(sid) playlist.warm_cooled_albums(sid) log.debug("advance", "Cooldown warming: %.6f" % (timestamp() - start_time,)) start_time = timestamp() _add_listener_count_record(sid) _trim(sid) user.trim_listeners(sid) cache.update_user_rating_acl(sid, history[sid][0].get_song().id) user.unlock_listeners(sid) db.c.update("UPDATE r4_listeners SET listener_voted_entry = NULL WHERE sid = %s", (sid,)) log.debug("advance", "User management and trimming: %.6f" % (timestamp() - start_time,)) start_time = timestamp() # reduce song blocks has to come first, otherwise it wll reduce blocks generated by _create_elections playlist.reduce_song_blocks(sid) # update_cache updates both the line and expiry times # this is expensive and must be done before and after every request is filled # DO THIS AFTER EVERYTHING ELSE, RIGHT BEFORE NEXT MANAGEMENT, OR PEOPLE'S REQUESTS SLIP THROUGH THE CRACKS request.update_line(sid) # add to the event list / update start times for events manage_next(sid) # update expire times AFTER manage_next, so people who aren't in line anymore don't see expiry times request.update_expire_times() log.debug("advance", "Request and upnext management: %.6f" % (timestamp() - start_time,)) update_memcache(sid) sync_to_front.sync_frontend_all(sid) db.c.commit() except: db.c.rollback() raise if current[sid] and config.has_station(sid, "tunein_partner_key") and config.get_station(sid, "tunein_partner_key"): ti_song = current[sid].get_song() if ti_song: ti_title = ti_song.data["title"] ti_album = ti_song.albums[0].data["name"] ti_artist = ", ".join([a.data["name"] for a in ti_song.artists]) params = { "id": config.get_station(sid, "tunein_id"), "title": ti_title, "artist": ti_artist, "album": ti_album, } try: req = requests.Request("GET", "http://air.radiotime.com/Playing.ashx", params=params) p = req.prepare() # Must be done here rather than in params because of odd strings TuneIn creates p.url += "&partnerId=%s" % config.get_station(sid, "tunein_partner_id") p.url += "&partnerKey=%s" % config.get_station(sid, "tunein_partner_key") s = requests.Session() resp = s.send(p, timeout=3) log.debug("advance", "TuneIn updated (%s): %s" % (resp.status_code, resp.text)) except Exception as e: log.exception("advance", "Could not update TuneIn.", e)
def _process_message(self, message, is_throttle_process=False): message_id = None if "message_id" in message: message_id = fieldtypes.zero_or_greater_integer(message['message_id']) throt_t = timestamp() - 3 self.msg_times = [ t for t in self.msg_times if t > throt_t ] if not is_throttle_process: self.msg_times.append(timestamp()) # log.debug("throttle", "%s - %s" % (len(self.msg_times), message['action'])) if self.throttled: # log.debug("throttle", "Currently throttled, adding to queue.") self.throttled_msgs.append(message) return elif len(self.msg_times) >= 5: # log.debug("throttle", "Too many messages, throttling.") self.throttled = True self.throttled_msgs.append(message) tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=0.5), self.process_throttle) return if message['action'] == "ping": self.write_message({ "pong": { "timestamp": timestamp() } }) return if message['action'] == "pong": self.write_message({ "pongConfirm": { "timestamp": timestamp() } }) return if message['action'] == "vote": zeromq.publish({ "action": "vote_by", "by": self.votes_by_key }) if message['action'] == "check_sched_current_id": self._do_sched_check(message) return message['action'] = "/api4/%s" % message['action'] if not message['action'] in api_endpoints: self.write_message({ "wserror": { "tl_key": "websocket_404", "text": self.locale.translate("websocket_404") } }) return endpoint = api_endpoints[message['action']](websocket=True) endpoint.locale = self.locale endpoint.request = FakeRequestObject(message, self.request.cookies) endpoint.sid = message['sid'] if ('sid' in message and message['sid']) else self.sid endpoint.user = self.user #pylint: disable=W0212 try: startclock = timestamp() # TODO: this should be a part of prepare_standalone! # it's required to see if another person on the same IP address has overriden the vote # for the in-memory user here, so it requires a DB fetch. if message['action'] == "/api4/vote" and self.user.is_anonymous(): self.user.refresh(self.sid) if "message_id" in message: if message_id == None: endpoint.prepare_standalone() raise APIException("invalid_argument", argument="message_id", reason=fieldtypes.zero_or_greater_integer_error, http_code=400) endpoint.prepare_standalone(message_id) else: endpoint.prepare_standalone() endpoint.post() endpoint.append("api_info", { "exectime": timestamp() - startclock, "time": round(timestamp()) }) if endpoint.sync_across_sessions: if endpoint.return_name in endpoint._output and isinstance(endpoint._output[endpoint.return_name], dict) and not endpoint._output[endpoint.return_name]['success']: pass else: zeromq.publish({ "action": "result_sync", "sid": self.sid, "user_id": self.user.id, "data": endpoint._output, "uuid_exclusion": self.uuid }) if message['action'] == "/api4/vote" and endpoint.return_name in endpoint._output and isinstance(endpoint._output[endpoint.return_name], dict) and endpoint._output[endpoint.return_name]['success']: live_voting = rainwave.schedule.update_live_voting(self.sid) endpoint.append("live_voting", live_voting) if self.should_vote_throttle(): zeromq.publish({ "action": "delayed_live_voting", "sid": self.sid, "uuid_exclusion": self.uuid, "data": { "live_voting": live_voting } }) else: zeromq.publish({ "action": "live_voting", "sid": self.sid, "uuid_exclusion": self.uuid, "data": { "live_voting": live_voting } }) except Exception as e: endpoint.write_error(500, exc_info=sys.exc_info(), no_finish=True) log.exception("websocket", "API Exception during operation.", e) finally: self.write_message(endpoint._output)
def get(self): # pylint: disable=E0202 ph = self._output[self.return_name] self.write(self.render_string("bare_header.html", title="%s" % ph["name"])) self.write("<script>\nwindow.top.refresh_all_screens = false;\n</script>") self.write("<h2>%s</h2>" % ph["name"]) self.write("<span>Times from the server:</span><br>") self.write( "<div style='font-family: monospace;'>%s</div>" % get_ph_formatted_time(ph["start"], ph["end"], "US/Eastern") ) self.write( "<div style='font-family: monospace;'>%s</div>" % get_ph_formatted_time(ph["start"], ph["end"], "US/Pacific") ) self.write( "<div style='font-family: monospace;'>%s</div>" % get_ph_formatted_time(ph["start"], ph["end"], "Europe/London") ) self.write( "<div style='font-family: monospace;'>%s</div>" % get_ph_formatted_time(ph["start"], ph["end"], "Asia/Tokyo") ) total_len = 0 for song in ph["songs"]: total_len += song["length"] self.write( "<br><div>Total length of songs: <b>%d:%02d</b></div>" % (int(total_len / 3600), (total_len / 60) % 60) ) self.write("<br><span>Change time. Use YOUR timezone.</span><br>") index.write_html_time_form(self, "power_hour", ph["start"]) self.write( "<br><button onclick=\"window.top.call_api('admin/change_producer_start_time', " ) self.write( "{ 'utc_time': document.getElementById('power_hour_timestamp').value, 'sched_id': %s });\"" % ph["id"] ) self.write(">Change Time</button></div><hr>") self.write( "<button onclick=\"window.top.call_api('admin/delete_producer', { 'sched_id': %s });\">Delete This Power Hour</button><hr>" % ph["id"] ) self.write( "Name: <input type='text' id='new_ph_name' value='%s'><br>" % ph["name"] ) self.write( "<button onclick=\"window.top.call_api('admin/change_producer_name', { 'sched_id': %s, 'name': document.getElementById('new_ph_name').value });\">Change Name</button><hr>" % ph["id"] ) self.write( "URL: <input type='text' id='new_ph_url' value='%s'><br>" % (ph["url"] or "") ) self.write( "<button onclick=\"window.top.call_api('admin/change_producer_url', { 'sched_id': %s, 'url': document.getElementById('new_ph_url').value });\">Change URL</button><hr>" % ph["id"] ) self.write( "<button onclick=\"window.top.call_api('admin/shuffle_power_hour', { 'sched_id': %s });\">Shuffle the Song Order</button><hr>\n\n" % ph["id"] ) try: self.write("<ol>") for song in ph["songs"]: self.write("<li><div>%s" % song["title"]) if song["one_up_used"]: self.write(" <b>(PLAYED)</b>") elif song["one_up_queued"]: self.write(" (queued)") self.write("</div><div>%s</div>\n" % song["albums"][0]["name"]) self.write("<div>") self.write( "<a onclick=\"window.top.call_api('admin/remove_from_power_hour', { 'one_up_id': %s });\">Delete</a> - " % song["one_up_id"] ) self.write( "<a onclick=\"window.top.call_api('admin/move_up_in_power_hour', { 'one_up_id': %s });\">Move Up</a>" % song["one_up_id"] ) self.write("</div></li>\n") self.write("</ol>\n") self.write( "<script>window.top.current_sched_id = %s;</script>\n\n" % ph["id"] ) except Exception as e: self.write("</ol>") self.write( "<div>ERROR DISPLAYING SONG LIST. Something is wrong. Consult Rob. Do not play this Power Hour.</div>" ) log.exception("admin", "Could not display song list.", e) self.write(self.render_string("basic_footer.html"))