def load(): for sid in config.station_ids: current[sid] = cache.get_station(sid, "sched_current") # If our cache is empty, pull from the DB if not current[sid]: current[sid] = get_event_in_progress(sid) if not current[sid] or not current[sid].get_song(): current[sid] = _create_election(sid) next[sid] = cache.get_station(sid, "sched_next") if not next[sid]: # pdb.set_trace() future_time = int(time.time()) + current[sid].length() next_elecs = event.Election.load_unused(sid) next_event = True next[sid] = [] while len(next[sid]) < 2 and next_event: next_event = get_event_at_time(sid, future_time) if not next_event: if len(next_elecs) > 0: next_event = next_elecs.pop(0) else: next_event = _create_election(sid, future_time) if next_event: future_time += next_event.length() next[sid].append(next_event) history[sid] = cache.get_station(sid, "sched_history") if not history[sid]: history[sid] = [] # Only loads elections but this should be good enough for history 99% of the time. for elec_id in db.c.fetch_list("SELECT elec_id FROM r4_elections WHERE elec_start_actual < %s ORDER BY elec_start_actual DESC LIMIT 5", (current[sid].start_actual,)): history[sid].insert(0, event.Election.load_by_id(elec_id))
def _do_sched_check(self, message): if not "sched_id" in message or not message["sched_id"]: self.write_message( { "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="sched_id"), } } ) return if not isinstance(message["sched_id"], numbers.Number): self.write_message( { "wserror": { "tl_key": "invalid_argument", "text": self.locale.translate("invalid_argument", argument="sched_id"), } } ) self._station_offline_check() if cache.get_station(self.sid, "sched_current_dict") and ( cache.get_station(self.sid, "sched_current_dict")["id"] != message["sched_id"] ): self.update() self.write_message({"outdated_data_warning": {"outdated": True}})
def load(): for sid in config.station_ids: current[sid] = cache.get_station(sid, "sched_current") # If our cache is empty, pull from the DB if not current[sid]: try: current[sid] = get_event_in_progress(sid) except ElectionDoesNotExist: current[sid] = event.Election(sid) if not current[sid]: raise ScheduleIsEmpty("Could not load or create any election for a current event.") next[sid] = cache.get_station(sid, "sched_next") if not next[sid]: future_time = time.time() + current[sid].get_length() next_elecs = event.Election.load_unused(sid) next_event = True next[sid] = [] while len(next) < 2 and next_event: next_event = get_event_at_time(sid, future_time) if not next_event: if length(next_elecs) > 0: next_event = next_elecs.pop(0) else: next_event = event.Election.create(sid) if next_event: future_time += next_event.get_length() next.append(next_event) history[sid] = cache.get_station(sid, "sched_history") if not history[sid]: history[sid] = [] song_ids = db.c.fetch_list("SELECT song_id FROM r4_song_history WHERE sid = %s ORDER BY songhist_id DESC") for id in song_ids: history[sid].append(playlist.Song.load_by_id(id, sid))
def load(): for sid in config.station_ids: current[sid] = cache.get_station(sid, "sched_current") # If our cache is empty, pull from the DB if not current[sid]: current[sid] = get_event_in_progress(sid) if not current[sid]: raise Exception("Could not load any events!") upnext[sid] = cache.get_station(sid, "sched_next") if not upnext[sid]: upnext[sid] = [] manage_next(sid) history[sid] = cache.get_station(sid, "sched_history") if not history[sid]: history[sid] = [] for song_id in db.c.fetch_list( "SELECT song_id FROM r4_song_history JOIN r4_song_sid USING (song_id, sid) JOIN r4_songs USING (song_id) WHERE sid = %s AND song_exists = TRUE AND song_verified = TRUE ORDER BY songhist_time DESC LIMIT 5", (sid,), ): history[sid].insert(0, events.singlesong.SingleSong(song_id, sid)) # create a fake history in case clients expect it without checking if not history[sid]: for i in range(1, 5): history[sid].insert( 0, events.singlesong.SingleSong( playlist.get_random_song_ignore_all(sid), sid ), )
def load(): for sid in config.station_ids: current[sid] = cache.get_station(sid, "sched_current") # If our cache is empty, pull from the DB if not current[sid]: current[sid] = get_event_in_progress(sid) if not current[sid]: current[sid] = _create_election(sid) next[sid] = cache.get_station(sid, "sched_next") if not next[sid]: # pdb.set_trace() future_time = int(time.time()) + current[sid].length() next_elecs = event.Election.load_unused(sid) next_event = True next[sid] = [] while len(next[sid]) < 2 and next_event: next_event = get_event_at_time(sid, future_time) if not next_event: if len(next_elecs) > 0: next_event = next_elecs.pop(0) else: next_event = _create_election(sid, future_time) if next_event: future_time += next_event.length() next[sid].append(next_event) history[sid] = cache.get_station(sid, "sched_history") if not history[sid]: history[sid] = [] song_ids = db.c.fetch_list("SELECT song_id FROM r4_song_history WHERE sid = %s ORDER BY songhist_id DESC", (sid,)) for id in song_ids: history[sid].append(playlist.Song.load_from_id(id, sid))
def reset_request_sequence(self): if _request_interval[self.sid] <= 0 and _request_sequence[ self.sid] <= 0: line_length = cache.get_station(self.sid, 'request_valid_positions') if not line_length: for entry in (cache.get_station(self.sid, "request_line") or []): if entry['song_id']: line_length += 1 log.debug( "requests", "Ready for sequence, entries in request line with valid songs: %s" % line_length) else: log.debug( "requests", "Ready for sequence, valid positions: %s" % line_length) # This sequence variable gets set AFTER a request has already been marked as fulfilled # If we have a +1 to this math we'll actually get 2 requests in a row, one now (is_request_needed will return true) # and then again when sequence_length will go from 1 to 0. _request_sequence[self.sid] = int( math.floor( line_length / config.get_station(self.sid, "request_sequence_scale"))) _request_interval[self.sid] = config.get_station( self.sid, "request_interval") log.debug("requests", "Sequence length: %s" % _request_sequence[self.sid])
def refresh(self): listener = self.get_listener_record() if listener: if self.data['sid'] == self.request_sid: self.data['radio_tuned_in'] = True elif self.request_sid == 0: self.request_sid = self.data['sid'] self.data['radio_tuned_in'] = True else: self.data['sid'] = self.request_sid # Default to All if no sid is given elif self.request_sid == 0: self.request_sid = 5 self.data['sid'] = 5 self.data['radio_tuned_in'] = False else: self.data['sid'] = self.request_sid self.data['radio_tuned_in'] = False if (self.id > 1) and cache.get_station(self.request_sid, "sched_current"): if cache.get_station(self.request_sid, "sched_current").get_dj_user_id() == self.id: self.data['radio_dj'] = True self.data['radio_request_position'] = self.get_request_line_position(self.data['sid']) self.data['radio_request_expires_at'] = self.get_request_expiry() if self.data['radio_tuned_in'] and not self.is_in_request_line() and self.has_requests(): self.put_in_request_line(self.data['sid']) if self.data['listener_lock'] and self.request_sid != self.data['listener_lock_sid']: self.data['listener_lock_in_effect'] = True
def _do_sched_check(self, message): if not "sched_id" in message or not message['sched_id']: self.write_message({ "wserror": { "tl_key": "missing_argument", "text": self.locale.translate("missing_argument", argument="sched_id") } }) return if not isinstance(message['sched_id'], numbers.Number): self.write_message({ "wserror": { "tl_key": "invalid_argument", "text": self.locale.translate("invalid_argument", argument="sched_id") } }) self._station_offline_check() if cache.get_station(self.sid, "sched_current_dict") and ( cache.get_station(self.sid, "sched_current_dict")['id'] != message['sched_id']): self.update() self.write_message({"outdated_data_warning": {"outdated": True}})
def load(): for sid in config.station_ids: current[sid] = cache.get_station(sid, "sched_current") # If our cache is empty, pull from the DB if not current[sid]: current[sid] = get_event_in_progress(sid) if not current[sid] or not current[sid].get_song(): current[sid] = _create_election(sid) next[sid] = cache.get_station(sid, "sched_next") if not next[sid]: # pdb.set_trace() future_time = int(time.time()) + current[sid].length() next_elecs = event.Election.load_unused(sid) next_event = True next[sid] = [] while len(next[sid]) < 2 and next_event: next_event = get_event_at_time(sid, future_time) if not next_event: if len(next_elecs) > 0: next_event = next_elecs.pop(0) else: next_event = _create_election(sid, future_time) if next_event: future_time += next_event.length() next[sid].append(next_event) history[sid] = cache.get_station(sid, "sched_history") if not history[sid]: history[sid] = [] # Only loads elections but this should be good enough for history 99% of the time. for elec_id in db.c.fetch_list( "SELECT elec_id FROM r4_elections WHERE elec_start_actual < %s ORDER BY elec_start_actual DESC LIMIT 5", (current[sid].start_actual, )): history[sid].insert(0, event.Election.load_by_id(elec_id))
def is_request_needed(self): global _request_interval global _request_sequence if not self.sid in _request_interval: _request_interval[self.sid] = cache.get_station(self.sid, "request_interval") if not _request_interval[self.sid]: _request_interval[self.sid] = 0 if not self.sid in _request_sequence: _request_sequence[self.sid] = cache.get_station(self.sid, "request_sequence") if not _request_sequence[self.sid]: _request_sequence[self.sid] = 0 # If we're ready for a request sequence, start one return_value = None if _request_interval[self.sid] <= 0 and _request_sequence[self.sid] <= 0: line_length = db.c.fetch_var("SELECT COUNT(*) FROM r4_request_line WHERE sid = %s", (self.sid,)) _request_sequence[self.sid] = 1 + math.floor(line_length / config.get_station(self.sid, "request_interval_scale")) _request_interval[self.sid] = config.get_station(self.sid, "request_interval_gap") return_value = True # If we are in a request sequence, do one elif _request_sequence[self.sid] > 0: return_value = True else: _request_interval[self.sid] -= 1 return_value = False cache.set_station(self.sid, "request_interval", _request_interval[self.sid]) cache.set_station(self.sid, "request_sequence", _request_sequence[self.sid]) return return_value
def load(): for sid in config.station_ids: current[sid] = cache.get_station(sid, "sched_current") # If our cache is empty, pull from the DB if not current[sid]: current[sid] = get_event_in_progress(sid) if not current[sid]: raise Exception("Could not load any events!") upnext[sid] = cache.get_station(sid, "sched_next") if not upnext[sid]: upnext[sid] = [] manage_next(sid) history[sid] = cache.get_station(sid, "sched_history") if not history[sid]: history[sid] = [] for song_id in db.c.fetch_list( "SELECT song_id FROM r4_song_history JOIN r4_song_sid USING (song_id, sid) JOIN r4_songs USING (song_id) WHERE sid = %s AND song_exists = TRUE AND song_verified = TRUE ORDER BY songhist_time DESC LIMIT 5", (sid,), ): history[sid].insert(0, events.singlesong.SingleSong(song_id, sid)) # create a fake history in case clients expect it without checking if not len(history[sid]): for i in range(1, 5): history[sid].insert(0, events.singlesong.SingleSong(playlist.get_random_song_ignore_all(sid), sid))
async def post(self): global sessions if self.user.is_dj(): self.dj = True else: self.dj = False api_requests.info.check_sync_status(self.sid, self.get_argument("offline_ack")) self.set_header("Content-Type", "application/json") if not self.get_argument("resync"): if ( self.get_argument("known_event_id") and cache.get_station(self.sid, "sched_current_dict") and ( cache.get_station(self.sid, "sched_current_dict")["id"] != self.get_argument("known_event_id") ) ): self.update() else: sessions[self.sid].append(self) self.wait_future = tornado.locks.Condition().wait() try: await self.wait_future except asyncio.CancelledError: return else: self.update()
def is_request_needed(self): global _request_interval global _request_sequence if not self.sid in _request_interval: _request_interval[self.sid] = cache.get_station(self.sid, "request_interval") if not _request_interval[self.sid]: _request_interval[self.sid] = 0 if not self.sid in _request_sequence: _request_sequence[self.sid] = cache.get_station(self.sid, "request_sequence") if not _request_sequence[self.sid]: _request_sequence[self.sid] = 0 log.debug("requests", "Interval %s // Sequence %s" % (_request_interval, _request_sequence)) # If we're ready for a request sequence, start one return_value = None if _request_interval[self.sid] <= 0 and _request_sequence[self.sid] <= 0: return_value = True # If we are in a request sequence, do one elif _request_sequence[self.sid] > 0: _request_sequence[self.sid] -= 1 log.debug("requests", "Still in sequence. Remainder: %s" % _request_sequence[self.sid]) return_value = True else: _request_interval[self.sid] -= 1 log.debug("requests", "Waiting on interval. Remainder: %s" % _request_interval[self.sid]) return_value = False cache.set_station(self.sid, "request_interval", _request_interval[self.sid]) cache.set_station(self.sid, "request_sequence", _request_sequence[self.sid]) return return_value
def rate(self, song_id, rating): if not self.user.data['rate_anything']: acl = cache.get_station(self.sid, "user_rating_acl") if not cache.get_station(self.sid, "sched_current").get_song().id == song_id: if not song_id in acl or not self.user.id in acl[song_id]: raise APIException("cannot_rate_now") elif not self.user.is_tunedin(): raise APIException("tunein_to_rate_current_song") albums = ratinglib.set_song_rating(self.sid, song_id, self.user.id, rating) self.append_standard("rating_submitted", updated_album_ratings = albums, song_id = song_id, rating_user = rating)
def attach_dj_info_to_request(request): request.append("dj_info", { "pause_requested": cache.get_station(request.sid, "backend_paused"), "pause_active": cache.get_station(request.sid, "backend_paused_playing"), "pause_title": cache.get_station(request.sid, "pause_title"), "dj_password": cache.get_station(request.sid, "dj_password"), "mount_host": config.get_station(request.sid, "liquidsoap_harbor_host"), "mount_port": config.get_station(request.sid, "liquidsoap_harbor_port"), "mount_url": config.get_station(request.sid, "liquidsoap_harbor_mount") })
def get_all_albums(sid, user = None, with_searchable = True): if with_searchable: if not user or user.is_anonymous(): return cache.get_station(sid, "all_albums") else: return playlist.get_all_albums_list(sid, user) else: if not user or user.is_anonymous(): return cache.get_station(sid, "all_albums_no_searchable") else: return playlist.get_all_albums_list(sid, user, with_searchable = False)
def attach_info_to_request(request, playlist = False, artists = False): # Front-load all non-animated content ahead of the schedule content # Since the schedule content is the most animated on R3, setting this content to load # first has a good impact on the perceived animation smoothness since table redrawing # doesn't have to take place during the first few frames. if request.user: request.append("user", request.user.to_private_dict()) if playlist or 'all_albums' in request.request.arguments: request.append("all_albums", api_requests.playlist.get_all_albums(request.sid, request.user)) else: request.append("album_diff", cache.get_station(request.sid, 'album_diff')) if artists or 'all_artists' in request.request.arguments: request.append("all_artists", cache.get_station(request.sid, 'all_artists')) request.append("request_line", cache.get_station(request.sid, "request_line")) # request.append("calendar", cache.get("calendar")) request.append("listeners_current", cache.get_station(request.sid, "listeners_current")) sched_next = [] sched_history = [] sched_current = {} if request.user: request.append("requests", request.user.get_requests()) sched_current = cache.get_station(request.sid, "sched_current") if request.user.is_tunedin(): sched_current.get_song().data['rating_allowed'] = True sched_current = sched_current.to_dict(request.user) for evt in cache.get_station(request.sid, "sched_next"): sched_next.append(evt.to_dict(request.user)) for evt in cache.get_station(request.sid, "sched_history"): sched_history.append(evt.to_dict(request.user, check_rating_acl=True)) else: sched_current = cache.get_station(request.sid, "sched_current_dict") sched_next = cache.get_station(request.sid, "sched_next_dict") sched_history = cache.get_station(request.sid, "sched_history_dict") request.append("sched_current", sched_current) request.append("sched_next", sched_next) request.append("sched_history", sched_history) if request.user: if not request.user.is_anonymous(): user_vote_cache = cache.get_user(request.user, "vote_history") temp_current = list() temp_current.append(sched_current) if user_vote_cache: for history in user_vote_cache: for event in (sched_history + sched_next + temp_current): if history[0] == event['id']: api_requests.vote.append_success_to_request(request, event['id'], history[1]) elif request.user.data['listener_voted_entry'] > 0 and request.user.data['listener_lock_sid'] == request.sid: api_requests.vote.append_success_to_request(request, sched_next[0].id, request.user.data['listener_voted_entry'])
def _get_pause_file(self): if not config.get("liquidsoap_annotations"): log.debug("backend", "Station is paused, using: %s" % config.get("pause_file")) return config.get("pause_file") string = "annotate:crossfade=\"2\",use_suffix=\"1\"," if cache.get_station(self.sid, "pause_title"): string += "title=\"%s\"" % cache.get_station(self.sid, "pause_title") else: string += "title=\"Intermission\"" string += ":" + config.get("pause_file") log.debug("backend", "Station is paused, using: %s" % string) return string
def get_all_albums(sid, user=None, with_searchable=True): if with_searchable: if not user or user.is_anonymous(): return cache.get_station(sid, "all_albums") else: return playlist.get_all_albums_list(sid, user) else: if not user or user.is_anonymous(): return cache.get_station(sid, "all_albums_no_searchable") else: return playlist.get_all_albums_list(sid, user, with_searchable=False)
def rate(self, song_id, rating): # if not self.user.data['radio_rate_anything']: acl = cache.get_station(self.sid, "user_rating_acl") if not cache.get_station(self.sid, "sched_current").get_song().id == song_id: if not song_id in acl or not self.user.id in acl[song_id]: raise APIException("cannot_rate_now") elif not self.user.is_tunedin(): raise APIException("tunein_to_rate_current_song") albums = ratinglib.set_song_rating(song_id, self.user.id, rating) self.append_standard("rating_submitted", updated_album_ratings=albums, song_id=song_id, rating_user=rating)
def get(self, sid): #pylint: disable=W0221 self.success = False self.sid = None if int(sid) in config.station_ids: self.sid = int(sid) else: return if cache.get_station(self.sid, "backend_paused") and cache.get_station(self.sid, "backend_pause_extend"): self.write(self._get_pause_file()) cache.set_station(self.sid, "backend_pause_extend", False) cache.set_station(self.sid, "backend_paused_playing", True) return else: cache.set_station(self.sid, "backend_pause_extend", False) cache.set_station(self.sid, "backend_paused", False) cache.set_station(self.sid, "backend_paused_playing", False) # This program must be run on 1 station for 1 instance, which would allow this operation to be safe. # Also works if 1 process is serving all stations. Pinging any instance for any station # would break the program here, though. if cache.get_station(self.sid, "get_next_socket_timeout") and sid_output[self.sid]: log.warn("backend", "Using previous output to prevent flooding.") self.write(sid_output[self.sid]) sid_output[self.sid] = None self.success = True else: try: schedule.advance_station(self.sid) except (psycopg2.OperationalError, psycopg2.InterfaceError) as e: log.warn("backend", e.diag.message_primary) db.close() db.connect() raise except psycopg2.extensions.TransactionRollbackError as e: log.warn("backend", "Database transaction deadlock. Re-opening database and setting retry timeout.") db.close() db.connect() raise to_send = None if not config.get("liquidsoap_annotations"): to_send = schedule.get_advancing_file(self.sid) else: to_send = self._get_annotated(schedule.get_advancing_event(self.sid)) sid_output[self.sid] = to_send self.success = True if not cache.get_station(self.sid, "get_next_socket_timeout"): self.write(to_send)
def post(self): global sessions if not cache.get_station(self.user.request_sid, "backend_ok") and not self.get_argument("offline_ack"): raise APIException("station_offline") self.set_header("Content-Type", "application/json") if not self.get_argument("resync"): if self.get_argument("known_event_id") and cache.get_station(self.sid, "sched_current_dict") and (cache.get_station(self.sid, "sched_current_dict")['id'] != self.get_argument("known_event_id")): self.update() else: sessions[self.sid].append(self) else: self.update()
def post(self): global sessions if not cache.get_station(self.sid, "backend_ok") and not self.get_argument("offline_ack"): raise APIException("station_offline") self.set_header("Content-Type", "application/json") if not self.get_argument("resync"): if self.get_argument("known_event_id") and cache.get_station(self.sid, "sched_current_dict") and (cache.get_station(self.sid, "sched_current_dict")['id'] != self.get_argument("known_event_id")): self.update() else: sessions[self.sid].append(self) else: self.update()
def _get_pause_file(self): if not config.get("liquidsoap_annotations"): log.debug( "backend", "Station is paused, using: %s" % config.get("pause_file")) return config.get("pause_file") string = 'annotate:crossfade="2",use_suffix="1",' if cache.get_station(self.sid, "pause_title"): string += 'title="%s"' % cache.get_station(self.sid, "pause_title") else: string += 'title="Intermission"' string += ":" + config.get("pause_file") log.debug("backend", "Station is paused, using: %s" % string) return string
def update_user(self): if not cache.get_station(self.user.request_sid, "backend_ok"): raise APIException("station_offline") self.user.refresh() self.append("user", self.user.to_private_dict()) self.finish()
def update(self, use_local_cache = False): # Front-load all non-animated content ahead of the schedule content # Since the schedule content is the most animated on R3, setting this content to load # first has a good impact on the perceived animation smoothness since table redrawing # doesn't have to take place during the first few frames. self.user.refresh(use_local_cache) self.append("user", self.user.get_public_dict()) if 'playlist' in self.request.arguments: self.append("all_albums", playlist.fetch_all_albums(self.user)) elif 'artist_list' in self.request.arguments: self.append("artist_list", playlist.fetch_all_artists(self.sid)) elif 'init' not in self.request.arguments: self.append("album_diff", cache.get_local_station(self.sid, 'album_diff')) if use_local_cache: self.append("requests_all", cache.get_local_station(self.sid, "request_all")) else: self.append("requests_all", cache.get_station(self.sid, "request_all")) self.append("requests_user", self.user.get_requests()) self.append("calendar", cache.local["calendar"]) self.append("listeners_current", cache.get_local_station(self.sid, "listeners_current")) self.append("sched_current", self.user.make_event_jsonable(cache.get_local_station(self.sid, "sched_current"), use_local_cache)) self.append("sched_next", self.user.make_events_jsonable(cache.get_local_station(self.sid, "sched_next"), use_local_cache)) self.append("sched_history", self.user.make_event_jsonable(cache.get_local_station(self.sid, "sched_history"), use_local_cache)) self.finish()
def get_next(sid): line = cache.get_station(sid, "request_line") if not line: return None song = None for pos in range(0, len(line)): if not line[pos]: pass # ?!?! elif 'skip' in line[pos] and line[pos]['skip']: log.debug("request", "Passing on user %s since they're marked as skippable." % line[pos]['username']) elif not line[pos]['song_id']: log.debug("request", "Passing on user %s since they have no valid first song." % line[pos]['username']) else: entry = line.pop(pos) song = playlist.Song.load_from_id(entry['song_id'], sid) log.debug("request", "Fulfilling %s's request for %s." % (entry['username'], song.filename)) song.data['elec_request_user_id'] = entry['user_id'] song.data['elec_request_username'] = entry['username'] u = User(entry['user_id']) db.c.update("DELETE FROM r4_request_store WHERE user_id = %s AND song_id = %s", (u.id, entry['song_id'])) u.remove_from_request_line() request_count = db.c.fetch_var("SELECT COUNT(*) FROM r4_request_history WHERE user_id = %s", (u.id,)) + 1 db.c.update("DELETE FROM r4_request_store WHERE song_id = %s AND user_id = %s", (song.id, u.id)) db.c.update("INSERT INTO r4_request_history (user_id, song_id, request_wait_time, request_line_size, request_at_count, sid) " "VALUES (%s, %s, %s, %s, %s, %s)", (u.id, song.id, timestamp() - entry['line_wait_start'], len(line), request_count, sid)) db.c.update("UPDATE phpbb_users SET radio_totalrequests = %s WHERE user_id = %s", (request_count, u.id)) song.update_request_count(sid) break return song
def _get_pause_file(self): if not config.get("liquidsoap_annotations"): log.debug( "backend", "Station is paused, using: %s" % config.get("pause_file")) return config.get("pause_file") string = "annotate:crossfade=\"2\",use_suffix=\"1\"," if cache.get_station(self.sid, "pause_title"): string += "title=\"%s\"" % cache.get_station( self.sid, "pause_title") else: string += "title=\"Intermission\"" string += ":" + config.get("pause_file") log.debug("backend", "Station is paused, using: %s" % string) return string
def prepare(self): super(MainIndex, self).prepare() if not cache.get_station(self.sid, "sched_current"): raise APIException( "server_just_started", "Rainwave is Rebooting, Please Try Again in a Few Minutes", http_code=500 ) # self.json_payload = {} self.jsfiles = None if not self.user: self.user = User(1) self.user.ip_address = self.request.remote_ip self.user.ensure_api_key() if self.beta or config.get("web_developer_mode") or config.get("developer_mode") or config.get("test_mode"): buildtools.bake_beta_css() buildtools.bake_beta_templates() self.jsfiles = [] for root, subdirs, files in os.walk( os.path.join(os.path.dirname(__file__), "../static/%s" % self.js_dir) ): # pylint: disable=W0612 for f in files: if f.endswith(".js"): self.jsfiles.append(os.path.join(root[root.find("static/%s" % self.js_dir) :], f))
def permission_checks(self): if (self.login_required or self.admin_required or self.dj_required) and (not self.user or self.user.is_anonymous()): raise APIException("login_required", http_code=403) if self.tunein_required and (not self.user or not self.user.is_tunedin()): raise APIException("tunein_required", http_code=403) if self.admin_required and (not self.user or not self.user.is_admin()): raise APIException("admin_required", http_code=403) if self.perks_required and (not self.user or not self.user.has_perks()): raise APIException("perks_required", http_code=403) if self.unlocked_listener_only and not self.user: raise APIException("auth_required", http_code=403) elif self.unlocked_listener_only and self.user.data['lock'] and self.user.data['lock_sid'] != self.sid: raise APIException("unlocked_only", station=config.station_id_friendly[self.user.data['lock_sid']], lock_counter=self.user.data['lock_counter'], http_code=403) is_dj = False if self.dj_required and not self.user: raise APIException("dj_required", http_code=403) if self.dj_required and not self.user.is_admin(): potential_djs = cache.get_station(self.sid, "dj_user_ids") if not potential_djs or not self.user.id in potential_djs: raise APIException("dj_required", http_code=403) is_dj = True self.user.data['dj'] = True elif self.dj_required and self.user.is_admin(): is_dj = True self.user.data['dj'] = True if self.dj_preparation and not is_dj and not self.user.is_admin(): if not db.c.fetch_var("SELECT COUNT(*) FROM r4_schedule WHERE sched_used = 0 AND sched_dj_user_id = %s", (self.user.id,)): raise APIException("dj_required", http_code=403)
def get_next(sid): line = cache.get_station(sid, "request_line") if not line: return None song = None for pos in range(0, len(line)): if not line[pos]: pass # ?!?! elif not line[pos]['song_id']: log.debug("request", "Passing on user %s since they have no valid first song." % line[pos]['username']) else: entry = line.pop(pos) song = playlist.Song.load_from_id(entry['song_id'], sid) log.debug("request", "Fulfilling %s's request for %s." % (entry['username'], song.filename)) song.data['elec_request_user_id'] = entry['user_id'] song.data['elec_request_username'] = entry['username'] u = User(entry['user_id']) db.c.update("DELETE FROM r4_request_store WHERE user_id = %s AND song_id = %s", (u.id, entry['song_id'])) u.remove_from_request_line() if u.has_requests(): u.put_in_request_line(u.get_tuned_in_sid()) request_count = db.c.fetch_var("SELECT COUNT(*) FROM r4_request_history WHERE user_id = %s", (u.id,)) + 1 db.c.update("DELETE FROM r4_request_store WHERE song_id = %s AND user_id = %s", (song.id, u.id)) db.c.update("INSERT INTO r4_request_history (user_id, song_id, request_wait_time, request_line_size, request_at_count, sid) " "VALUES (%s, %s, %s, %s, %s, %s)", (u.id, song.id, time.time() - entry['line_wait_start'], len(line), request_count, sid)) db.c.update("UPDATE phpbb_users SET radio_totalrequests = %s WHERE user_id = %s", (request_count, u.id)) song.update_request_count(sid) # If we fully update the line, the user may sneak in and get 2 requests in the same election. # This is not a good idea, so we leave it to the scheduler to issue the full cache update. cache.set_station(sid, "request_line", line, True) break return song
def get_next(sid): line = cache.get_station(sid, "request_line") if not line: return None song = None for pos in range(0, len(line)): if not line[pos] or not line[pos]['song_id']: pass else: entry = line.pop(pos) song = playlist.Song.load_from_id(entry['song_id'], sid) song.data['elec_request_user_id'] = entry['user_id'] song.data['elec_request_username'] = entry['username'] u = User(entry['user_id']) db.c.update("DELETE FROM r4_request_store WHERE user_id = %s AND song_id = %s", (u.id, entry['song_id'])) u.remove_from_request_line() user_sid = u.get_tuned_in_sid() if u.has_requests(): u.put_in_request_line(user_sid) request_count = db.c.fetch_var("SELECT COUNT(*) FROM r4_request_history WHERE user_id = %s", (u.id,)) + 1 db.c.update("DELETE FROM r4_request_store WHERE song_id = %s AND user_id = %s", (song.id, u.id)) db.c.update("INSERT INTO r4_request_history (user_id, song_id, request_wait_time, request_line_size, request_at_count) " "VALUES (%s, %s, %s, %s, %s)", (u.id, song.id, time.time() - entry['line_wait_start'], len(line), request_count)) # Update the user's request cache u.get_requests(refresh=True) cache.set_station(sid, "request_line", line, True) break return song
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 get_updated_albums_dict(sid): global updated_album_ids if not sid in updated_album_ids: return [] previous_newest_album = cache.get_station(sid, "newest_album") if not previous_newest_album: cache.set_station(sid, "newest_album", timestamp()) else: newest_albums = db.c.fetch_list( "SELECT album_id FROM r4_albums JOIN r4_album_sid USING (album_id) WHERE sid = %s AND album_added_on > %s", (sid, previous_newest_album), ) for album_id in newest_albums: updated_album_ids[sid][album_id] = True cache.set_station(sid, "newest_album", timestamp()) album_diff = [] for album_id in updated_album_ids[sid]: album = Album.load_from_id_sid(album_id, sid) album.solve_cool_lowest(sid) tmp = album.to_dict_full() # Remove user-related stuff since this gets stuffed straight down the pipe tmp.pop("rating_user", None) tmp.pop("fave", None) album_diff.append(tmp) return album_diff
def reset_request_sequence(self): if _request_interval[self.sid] <= 0 and _request_sequence[self.sid] <= 0: line_length = cache.get_station(self.sid, 'request_valid_positions') if not line_length: for entry in (cache.get_station(self.sid, "request_line") or []): if entry['song_id']: line_length += 1 log.debug("requests", "Ready for sequence, entries in request line with valid songs: %s" % line_length) else: log.debug("requests", "Ready for sequence, valid positions: %s" % line_length) # This sequence variable gets set AFTER a request has already been marked as fulfilled # If we have a +1 to this math we'll actually get 2 requests in a row, one now (is_request_needed will return true) # and then again when sequence_length will go from 1 to 0. _request_sequence[self.sid] = int(math.floor(line_length / config.get_station(self.sid, "request_sequence_scale"))) _request_interval[self.sid] = config.get_station(self.sid, "request_interval") log.debug("requests", "Sequence length: %s" % _request_sequence[self.sid])
def prepare(self): if self.path_kwargs.get("station"): self.sid = config.stream_filename_to_sid.get( self.path_kwargs["station"]) if not self.sid: self.redirect(config.get("base_site_url")) return super(MainIndex, self).prepare() if self.path_kwargs.get("station") is None: self.redirect("{}{}/".format( config.get("base_site_url"), config.station_mount_filenames[self.sid], )) return if config.get("enforce_ssl") and self.request.protocol != "https": self.redirect("{}{}/".format( config.get("base_site_url"), config.station_mount_filenames[self.sid], )) return if not config.get("developer_mode" ) and self.request.host != config.get("hostname"): self.redirect("{}{}/".format( config.get("base_site_url"), config.station_mount_filenames[self.sid], )) return if not cache.get_station(self.sid, "sched_current"): raise APIException( "server_just_started", "Rainwave is Rebooting, Please Try Again in a Few Minutes", http_code=500, ) self.jsfiles = None if not self.user: self.user = User(1) self.user.ip_address = self.request.remote_ip self.user.ensure_api_key() if (self.beta or config.get("web_developer_mode") or config.get("developer_mode") or config.get("test_mode")): buildtools.bake_beta_css() buildtools.bake_beta_templates() self.jsfiles = [] for root, _subdirs, files in os.walk( os.path.join(os.path.dirname(__file__), "../static/%s" % self.js_dir)): for f in files: if f.endswith(".js"): self.jsfiles.append( os.path.join( root[root.find("static/%s" % self.js_dir):], f))
def get_updated_albums_dict(sid): global updated_album_ids if not sid in updated_album_ids: return [] previous_newest_album = cache.get_station(sid, "newest_album") if not previous_newest_album: cache.set_station(sid, "newest_album", timestamp()) else: newest_albums = db.c.fetch_list( "SELECT album_id FROM r4_albums JOIN r4_album_sid USING (album_id) WHERE sid = %s AND album_added_on > %s", (sid, previous_newest_album)) for album_id in newest_albums: updated_album_ids[sid][album_id] = True cache.set_station(sid, "newest_album", timestamp()) album_diff = [] for album_id in updated_album_ids[sid]: album = Album.load_from_id_sid(album_id, sid) album.solve_cool_lowest(sid) tmp = album.to_dict_full() # Remove user-related stuff since this gets stuffed straight down the pipe tmp.pop('rating_user', None) tmp.pop('fave', None) album_diff.append(tmp) return album_diff
def dj_heartbeat_check(): # Don't do this in testing environments if config.get("developer_mode"): return for sid in config.station_ids: if cache.get_station(sid, "backend_paused_playing"): hb = cache.get_station(sid, "dj_heartbeat") hbs = cache.get_station(sid, "dj_heartbeat_start") if not hbs or ((timestamp() - hbs) <= 10): pass elif not hb or ((timestamp() - hb) >= 15): log.warn("dj_heartbeat", "DJ heart attack - resetting station to normal.") cache.set_station(sid, "backend_paused", False) cache.set_station(sid, "backend_paused_playing", False) liquidsoap.kick_dj(sid) liquidsoap.skip(sid)
def post(self): lock_count = 0 voted = False elec_id = None for event in cache.get_station(self.sid, "sched_next"): lock_count += 1 if (event.is_election and event.has_entry_id(self.get_argument("entry_id")) and len(event.songs) > 1): elec_id = event.id voted = self.vote(self.get_argument("entry_id"), event, lock_count) break if not self.user.data["perks"]: break if voted: append_success_to_request(self, elec_id, self.get_argument("entry_id")) else: self.append_standard( "cannot_vote_for_this_now", success=False, elec_id=elec_id, entry_id=self.get_argument("entry_id"), )
def refresh(self): listener = self.get_listener_record(use_cache=False) if listener: if self.data['sid'] == self.request_sid: self.data['tuned_in'] = True elif not self.request_sid: self.request_sid = self.data['sid'] self.data['tuned_in'] = True else: self.data['sid'] = self.request_sid # Default to All if no sid is given elif not self.request_sid: self.request_sid = 5 self.data['sid'] = 5 self.data['tuned_in'] = False else: self.data['sid'] = self.request_sid self.data['tuned_in'] = False if (self.id > 1) and cache.get_station(self.request_sid, "sched_current"): self.data['request_position'] = self.get_request_line_position( self.data['sid']) self.data['request_expires_at'] = self.get_request_expiry() if self.data['tuned_in'] and not self.is_in_request_line( ) and self.has_requests(): self.put_in_request_line(self.data['sid']) if self.data['lock'] and self.request_sid != self.data['lock_sid']: self.data['lock_in_effect'] = True
def prepare(self): super(MainIndex, self).prepare() if not cache.get_station(self.sid, "sched_current"): raise APIException( "server_just_started", "Rainwave is Rebooting, Please Try Again in a Few Minutes", http_code=500) # self.json_payload = {} self.jsfiles = None if not self.user: self.user = User(1) self.user.ensure_api_key() if self.beta or config.get("web_developer_mode") or config.get( "developer_mode") or config.get("test_mode"): buildtools.bake_beta_css() buildtools.bake_beta_templates() self.jsfiles = [] for root, subdirs, files in os.walk( os.path.join(os.path.dirname(__file__), "../static/%s" % self.js_dir)): #pylint: disable=W0612 for f in files: if f.endswith(".js"): self.jsfiles.append( os.path.join( root[root.find("static/%s" % self.js_dir):], f))
def is_request_needed(self): global _request_interval global _request_sequence if not self.sid in _request_interval: _request_interval[self.sid] = cache.get_station( self.sid, "request_interval") if not _request_interval[self.sid]: _request_interval[self.sid] = 0 if not self.sid in _request_sequence: _request_sequence[self.sid] = cache.get_station( self.sid, "request_sequence") if not _request_sequence[self.sid]: _request_sequence[self.sid] = 0 log.debug( "requests", "Interval %s // Sequence %s" % (_request_interval, _request_sequence), ) # If we're ready for a request sequence, start one return_value = None if _request_interval[self.sid] <= 0 and _request_sequence[ self.sid] <= 0: return_value = True # If we are in a request sequence, do one elif _request_sequence[self.sid] > 0: _request_sequence[self.sid] -= 1 log.debug( "requests", "Still in sequence. Remainder: %s" % _request_sequence[self.sid], ) return_value = True else: _request_interval[self.sid] -= 1 log.debug( "requests", "Waiting on interval. Remainder: %s" % _request_interval[self.sid], ) return_value = False cache.set_station(self.sid, "request_interval", _request_interval[self.sid]) cache.set_station(self.sid, "request_sequence", _request_sequence[self.sid]) return return_value
def get(self, sid): self.success = False self.sid = None if int(sid) in config.station_ids: self.sid = int(sid) else: return if cache.get_station(self.sid, "backend_paused"): if not cache.get_station(self.sid, "dj_heartbeat_start"): log.debug("dj", "Setting server start heatbeat.") cache.set_station(self.sid, "dj_heartbeat_start", timestamp()) self.write(self._get_pause_file()) schedule.set_upnext_crossfade(self.sid, False) cache.set_station(self.sid, "backend_paused_playing", True) sync_to_front.sync_frontend_dj(self.sid) return else: cache.set_station(self.sid, "dj_heartbeat_start", False) cache.set_station(self.sid, "backend_paused", False) cache.set_station(self.sid, "backend_paused_playing", False) try: schedule.advance_station(self.sid) except (psycopg2.OperationalError, psycopg2.InterfaceError) as e: log.warn("backend", e.diag.message_primary) db.close() db.connect() raise except psycopg2.extensions.TransactionRollbackError as e: log.warn( "backend", "Database transaction deadlock. Re-opening database and setting retry timeout.", ) db.close() db.connect() raise to_send = None if not config.get("liquidsoap_annotations"): to_send = schedule.get_advancing_file(self.sid) else: to_send = self._get_annotated( schedule.get_advancing_event(self.sid)) self.success = True if not cache.get_station(self.sid, "get_next_socket_timeout"): self.write(to_send)
def post(self): self.append(self.return_name, cache.get_station(self.sid, "request_line")) self.append( "request_line_db", db.c.fetch_all( "SELECT username, r4_request_line.* FROM r4_request_line JOIN phpbb_users USING (user_id) WHERE sid = %s ORDER BY line_wait_start", (self.sid, )))
def post(self): if not cache.get_station(self.sid, "backend_paused"): result = "Station seems unpaused already. " else: result = "Unpausing station. " cache.set_station(self.sid, "backend_paused", False) cache.set_station(self.sid, "backend_pause_extend", False) if (cache.get_station(self.sid, "backend_paused_playing")): result += "Automatically starting music. " result += "\n" result += liquidsoap.skip(self.sid) else: result += "If station remains silent, music will start playing within 5 minutes unless you hit skip." if (self.get_argument("kick_dj", default=False)): result += "Kicking DJ. " result += "\n" result += liquidsoap.kick_dj(self.sid) self.append(self.return_name, { "success": True, "message": result })
def update_user(self): self._startclock = time.time() if not cache.get_station(self.sid, "backend_ok"): raise APIException("station_offline") self.user.refresh(self.sid) self.append("user", self.user.to_private_dict()) self.finish()
def _station_offline_check(self): if not cache.get_station(self.sid, "backend_ok"): # shamelessly fake an error. self.write_message({ "sync_result": { "tl_key": "station_offline", "text": self.locale.translate("station_offline") } })
def post(self): if not cache.get_station(self.sid, "backend_paused"): result = "Station seems unpaused already. " else: result = "Unpausing station. " cache.set_station(self.sid, "backend_paused", False) if (cache.get_station(self.sid, "backend_paused_playing")): result += "Automatically starting music. " result += "\n" result += liquidsoap.skip(self.sid) else: result += "If station remains silent, music will start playing within 5 minutes unless you hit skip." if (self.get_argument("kick_dj", default=False)): result += "Kicking DJ. " result += "\n" result += liquidsoap.kick_dj(self.sid) attach_dj_info_to_request(self) self.append(self.return_name, { "success": True, "message": result })
def update(self): # Overwrite this value since who knows how long we've spent idling self._startclock = time.time() if not cache.get_station(self.user.request_sid, "backend_ok"): raise APIException("station_offline") self.user.refresh() api_requests.info.attach_info_to_request(self) self.finish()
def load(): for sid in config.station_ids: current[sid] = cache.get_station(sid, "sched_current") # If our cache is empty, pull from the DB if not current[sid]: current[sid] = get_event_in_progress(sid) if not current[sid]: raise Exception("Could not load any events!") upnext[sid] = cache.get_station(sid, "sched_next") if not upnext[sid]: upnext[sid] = [] manage_next(sid) history[sid] = cache.get_station(sid, "sched_history") if not history[sid]: history[sid] = [] for song_id in db.c.fetch_list("SELECT song_id FROM r4_song_history WHERE sid = %s ORDER BY songhist_time DESC LIMIT 5", (sid,)): history[sid].insert(0, events.event.SingleSong(song_id, sid))