Exemple #1
0
    def get(self):
        live_events = EventHelper.getEventsWithinADay()
        for event in live_events:
            taskqueue.add(url='/tasks/math/do/predict_match_times/{}'.format(
                event.key_name),
                          method='GET')
        # taskqueue.add(url='/tasks/do/bluezone_update', method='GET')

        # Clear down events for events that aren't live
        status_sitevar = Sitevar.get_by_id('apistatus.down_events')
        if status_sitevar is not None:
            live_event_keys = set([e.key.id() for e in live_events])

            old_status = set(status_sitevar.contents)
            new_status = old_status.copy()
            for event_key in old_status:
                if event_key not in live_event_keys:
                    new_status.remove(event_key)
            status_sitevar.contents = list(new_status)
            status_sitevar.put()

            # Clear API Response cache
            ApiStatusController.clear_cache_if_needed(old_status, new_status)

        self.response.out.write(
            "Enqueued time prediction for {} events".format(len(live_events)))
    def post(self):
        self._require_admin()

        trusted_sitevar = Sitevar.get_or_insert('trustedapi')
        sitevar = Sitevar.get_or_insert('apistatus')
        old_value = sitevar.contents

        status = {}
        status['android'] = {}
        status['ios'] = {}
        status['max_season'] = int(self.request.get('max_year'))
        status['current_season'] = int(self.request.get('current_year'))
        status['android']['latest_app_version'] = int(self.request.get('android_latest_version'))
        status['android']['min_app_version'] = int(self.request.get('android_min_version'))
        status['ios']['latest_app_version'] = int(self.request.get('ios_latest_version'))
        status['ios']['min_app_version'] = int(self.request.get('ios_min_version'))
        sitevar.contents = status
        sitevar.put()

        trusted_status = {
            1: True if self.request.get('enable_match_video') else False,
            2: True if self.request.get('enable_event_teams') else False,
            3: True if self.request.get('enable_event_matches') else False,
            4: True if self.request.get('enable_event_rankings') else False,
            5: True if self.request.get('enable_event_alliances') else False,
            6: True if self.request.get('enable_event_awards') else False,
        }
        trusted_sitevar.contents = trusted_status
        trusted_sitevar.put()

        ApiStatusController.clear_cache_if_needed(old_value, status)

        self.redirect('/admin/apistatus')
Exemple #3
0
    def send_upcoming_matches(cls, live_events):
        from helpers.match_helper import MatchHelper  # PJL: Hacky :P
        # Causes circular import, otherwise
        # https://github.com/the-blue-alliance/the-blue-alliance/pull/1098#discussion_r25128966

        down_events = []
        now = datetime.datetime.utcnow()
        for event in live_events:
            matches = event.matches
            if not matches:
                continue
            last_matches = MatchHelper.recentMatches(matches, num=1)
            next_matches = MatchHelper.upcomingMatches(matches, num=2)

            # First, compare the difference between scheduled times of next/last match
            # Send an upcoming notification if it's <10 minutes, to account for events ahead of schedule
            if last_matches != []:
                last_match = last_matches[0]
                for i, next_match in enumerate(next_matches):
                    if not next_match.push_sent and last_match.time and next_match.time:
                        diff = next_match.time - last_match.time
                        if diff < datetime.timedelta(minutes=10 * (i + 1)):
                            cls.send_upcoming_match_notification(
                                next_match, event)

            for match in next_matches:
                if match and not match.push_sent:
                    # Only continue sending for the next match if a push hasn't already been sent for it
                    if match.time is None or match.time + datetime.timedelta(
                            minutes=-7) <= now:
                        # Only send notifications for matches no more than 7 minutes (average-ish match cycle time) before it's scheduled to start
                        # Unless, the match has no time info. Then #yolo and send it
                        cls.send_upcoming_match_notification(match, event)

            # Determine if event is down
            if cls.is_event_down(last_matches[0] if last_matches else None,
                                 next_matches[0] if next_matches else None):
                down_events.append(event.key_name)

        # Update the status sitevar
        status_sitevar = Sitevar.get_by_id('apistatus.down_events')
        if status_sitevar is None:
            status_sitevar = Sitevar(id="apistatus.down_events",
                                     description="A list of down event keys",
                                     values_json="[]")
        old_status = status_sitevar.contents

        status_sitevar.contents = down_events
        status_sitevar.put()

        # Clear API Response cache
        ApiStatusController.clear_cache_if_needed(old_status, down_events)
Exemple #4
0
    def get(self, event_key):
        import pytz

        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)

        matches = event.matches
        if not matches or not event.timezone_id:
            return

        timezone = pytz.timezone(event.timezone_id)
        played_matches = MatchHelper.recentMatches(matches, num=0)
        unplayed_matches = MatchHelper.upcomingMatches(matches,
                                                       num=len(matches))
        MatchTimePredictionHelper.predict_future_matches(
            event_key, played_matches, unplayed_matches, timezone,
            event.within_a_day)

        # Detect whether the event is down
        # An event NOT down if ANY unplayed match's predicted time is within its scheduled time by a threshold and
        # the last played match (if it exists) wasn't too long ago.
        event_down = len(unplayed_matches) > 0
        for unplayed_match in unplayed_matches:
            if ((unplayed_match.predicted_time and unplayed_match.time
                 and unplayed_match.predicted_time <
                 unplayed_match.time + datetime.timedelta(minutes=30)) or
                (played_matches == [] or played_matches[-1].actual_time is None
                 or played_matches[-1].actual_time >
                 datetime.datetime.now() - datetime.timedelta(minutes=30))):
                event_down = False
                break

        status_sitevar = Sitevar.get_by_id('apistatus.down_events')
        if status_sitevar is None:
            status_sitevar = Sitevar(id="apistatus.down_events",
                                     description="A list of down event keys",
                                     values_json="[]")
        old_status = set(status_sitevar.contents)
        new_status = old_status.copy()
        if event_down:
            new_status.add(event_key)
        elif event_key in new_status:
            new_status.remove(event_key)

        status_sitevar.contents = list(new_status)
        status_sitevar.put()

        # Clear API Response cache
        ApiStatusController.clear_cache_if_needed(old_status, new_status)
    def send_upcoming_matches(cls, live_events):
        from helpers.match_helper import MatchHelper  # PJL: Hacky :P
        # Causes circular import, otherwise
        # https://github.com/the-blue-alliance/the-blue-alliance/pull/1098#discussion_r25128966

        down_events = []
        now = datetime.datetime.utcnow()
        for event in live_events:
            matches = event.matches
            if not matches:
                continue
            last_matches = MatchHelper.recentMatches(matches, num=1)
            next_matches = MatchHelper.upcomingMatches(matches, num=2)

            # First, compare the difference between scheduled times of next/last match
            # Send an upcoming notification if it's <10 minutes, to account for events ahead of schedule
            if last_matches != []:
                last_match = last_matches[0]
                for i, next_match in enumerate(next_matches):
                    if not next_match.push_sent and last_match.time and next_match.time:
                        diff = next_match.time - last_match.time
                        if diff < datetime.timedelta(minutes=10 * (i + 1)):
                            cls.send_upcoming_match_notification(next_match, event)

            for match in next_matches:
                if match and not match.push_sent:
                    # Only continue sending for the next match if a push hasn't already been sent for it
                    if match.time is None or match.time + datetime.timedelta(minutes=-7) <= now:
                        # Only send notifications for matches no more than 7 minutes (average-ish match cycle time) before it's scheduled to start
                        # Unless, the match has no time info. Then #yolo and send it
                        cls.send_upcoming_match_notification(match, event)

            # Determine if event is down
            if cls.is_event_down(last_matches[0] if last_matches else None, next_matches[0] if next_matches else None):
                down_events.append(event.key_name)

        # Update the status sitevar
        status_sitevar = Sitevar.get_by_id('apistatus.down_events')
        if status_sitevar is None:
            status_sitevar = Sitevar(id="apistatus.down_events", description="A list of down event keys", values_json="[]")
        old_status = status_sitevar.contents

        status_sitevar.contents = down_events
        status_sitevar.put()

        # Clear API Response cache
        ApiStatusController.clear_cache_if_needed(old_status, down_events)
    def post(self, sitevar_key):
        self._require_admin()

        # note, we don't use sitevar_key

        sitevar = Sitevar(
            id=self.request.get("key"),
            description=self.request.get("description"),
            values_json=self.request.get("values_json"),
        )
        sitevar.put()

        # If we're changing an apistatus sitevar, clear the API response cache
        # Since this will be used rarely, always clear the cache on update
        if 'apistatus' in self.request.get("key"):
            ApiStatusController.clear_cache_if_needed(False, True)

        self.redirect("/admin/sitevar/edit/" + sitevar.key.id() + "?success=true")
    def post(self, sitevar_key):
        self._require_admin()

        # note, we don't use sitevar_key

        sitevar = Sitevar(
            id=self.request.get("key"),
            description=self.request.get("description"),
            values_json=self.request.get("values_json"),
        )
        sitevar.put()

        # If we're changing an apistatus sitevar, clear the API response cache
        # Since this will be used rarely, always clear the cache on update
        if 'apistatus' in self.request.get("key"):
            ApiStatusController.clear_cache_if_needed(False, True)

        self.redirect("/admin/sitevar/edit/" + sitevar.key.id() +
                      "?success=true")
    def get(self, event_key):
        import pytz

        event = Event.get_by_id(event_key)
        if not event:
            self.abort(404)

        matches = event.matches
        if not matches or not event.timezone_id:
            return

        timezone = pytz.timezone(event.timezone_id)
        played_matches = MatchHelper.recentMatches(matches, num=0)
        unplayed_matches = MatchHelper.upcomingMatches(matches, num=len(matches))
        MatchTimePredictionHelper.predict_future_matches(event_key, played_matches, unplayed_matches, timezone, event.within_a_day)

        # Detect whether the event is down
        # An event NOT down if ANY unplayed match's predicted time is within its scheduled time by a threshold and
        # the last played match (if it exists) wasn't too long ago.
        event_down = len(unplayed_matches) > 0
        for unplayed_match in unplayed_matches:
            if ((unplayed_match.predicted_time and unplayed_match.time and
                unplayed_match.predicted_time < unplayed_match.time + datetime.timedelta(minutes=30)) or
                (played_matches == [] or played_matches[-1].actual_time is None or played_matches[-1].actual_time > datetime.datetime.now() - datetime.timedelta(minutes=30))):
                event_down = False
                break

        status_sitevar = Sitevar.get_by_id('apistatus.down_events')
        if status_sitevar is None:
            status_sitevar = Sitevar(id="apistatus.down_events", description="A list of down event keys", values_json="[]")
        old_status = set(status_sitevar.contents)
        new_status = old_status.copy()
        if event_down:
            new_status.add(event_key)
        elif event_key in new_status:
            new_status.remove(event_key)

        status_sitevar.contents = list(new_status)
        status_sitevar.put()

        # Clear API Response cache
        ApiStatusController.clear_cache_if_needed(old_status, new_status)
Exemple #9
0
    def post(self):
        self._require_admin()

        sitevar = Sitevar.get_or_insert('apistatus')
        old_value = sitevar.contents

        status = {}
        status['android'] = {}
        status['ios'] = {}
        status['max_season'] = int(self.request.get('max_year'))
        status['android']['latest_app_version'] = int(self.request.get('android_latest_version'))
        status['android']['min_app_version'] = int(self.request.get('android_min_version'))
        status['ios']['latest_app_version'] = int(self.request.get('ios_latest_version'))
        status['ios']['min_app_version'] = int(self.request.get('ios_min_version'))
        sitevar.contents = status
        sitevar.put()

        ApiStatusController.clear_cache_if_needed(old_value, status)

        self.redirect('/admin/apistatus')
    def post(self):
        self._require_admin()

        trusted_sitevar = Sitevar.get_or_insert('trustedapi')
        sitevar = Sitevar.get_or_insert('apistatus')
        old_value = sitevar.contents

        status = {}
        status['android'] = {}
        status['ios'] = {}
        status['max_season'] = int(self.request.get('max_year'))
        status['current_season'] = int(self.request.get('current_year'))
        status['android']['latest_app_version'] = int(
            self.request.get('android_latest_version'))
        status['android']['min_app_version'] = int(
            self.request.get('android_min_version'))
        status['ios']['latest_app_version'] = int(
            self.request.get('ios_latest_version'))
        status['ios']['min_app_version'] = int(
            self.request.get('ios_min_version'))
        sitevar.contents = status
        sitevar.put()

        trusted_status = {
            1: True if self.request.get('enable_match_video') else False,
            2: True if self.request.get('enable_event_teams') else False,
            3: True if self.request.get('enable_event_matches') else False,
            4: True if self.request.get('enable_event_rankings') else False,
            5: True if self.request.get('enable_event_alliances') else False,
            6: True if self.request.get('enable_event_awards') else False,
        }
        trusted_sitevar.contents = trusted_status
        trusted_sitevar.put()

        ApiStatusController.clear_cache_if_needed(old_value, status)

        self.redirect('/admin/apistatus')
    def get(self):
        live_events = EventHelper.getEventsWithinADay()
        for event in live_events:
            taskqueue.add(url='/tasks/math/do/predict_match_times/{}'.format(event.key_name),
                          method='GET')
        # taskqueue.add(url='/tasks/do/bluezone_update', method='GET')

        # Clear down events for events that aren't live
        status_sitevar = Sitevar.get_by_id('apistatus.down_events')
        if status_sitevar is not None:
            live_event_keys = set([e.key.id() for e in live_events])

            old_status = set(status_sitevar.contents)
            new_status = old_status.copy()
            for event_key in old_status:
                if event_key not in live_event_keys:
                    new_status.remove(event_key)
            status_sitevar.contents = list(new_status)
            status_sitevar.put()

            # Clear API Response cache
            ApiStatusController.clear_cache_if_needed(old_status, new_status)

        self.response.out.write("Enqueued time prediction for {} events".format(len(live_events)))
                'Cache-Control': 'no-cache, max-age=10',
                'Pragma': 'no-cache',
            }
            try:
                result = yield context.urlfetch(url, headers=headers)
            except Exception, e:
                logging.error("URLFetch failed for: {}".format(url))
                logging.info(e)
                raise ndb.Return(None)

        old_status = self._is_down_sitevar.contents
        if result.status_code == 200:
            if old_status == True:
                self._is_down_sitevar.contents = False
                self._is_down_sitevar.put()
            ApiStatusController.clear_cache_if_needed(old_status, self._is_down_sitevar.contents)

            # Save raw API response into cloudstorage
            if self._save_response and tba_config.CONFIG['save-frc-api-response']:
                try:
                    # Check for last response
                    last_item = None
                    for last_item in gcs_dir_contents:
                        pass

                    write_new = True
                    if last_item is not None:
                        with cloudstorage.open(last_item.filename, 'r') as last_json_file:
                            if last_json_file.read() == result.content:
                                write_new = False  # Do not write if content didn't change
                'Cache-Control': 'no-cache, max-age=10',
                'Pragma': 'no-cache',
            }
            try:
                result = yield context.urlfetch(url, headers=headers)
            except Exception, e:
                logging.error("URLFetch failed for: {}".format(url))
                logging.info(e)
                raise ndb.Return(None)

        old_status = self._is_down_sitevar.contents
        if result.status_code == 200:
            if old_status == True:
                self._is_down_sitevar.contents = False
                self._is_down_sitevar.put()
            ApiStatusController.clear_cache_if_needed(old_status, self._is_down_sitevar.contents)

            # Save raw API response into cloudstorage
            if self._save_response and tba_config.CONFIG['save-frc-api-response']:
                try:
                    # Check for last response
                    last_item = None
                    for last_item in gcs_dir_contents:
                        pass

                    write_new = True
                    if last_item is not None:
                        with cloudstorage.open(last_item.filename, 'r') as last_json_file:
                            if last_json_file.read() == result.content:
                                write_new = False  # Do not write if content didn't change
Exemple #14
0
class DatafeedFMSAPI(object):
    EVENT_SHORT_EXCEPTIONS = {
        'arc': 'archimedes',
        'cars': 'carson',
        'carv': 'carver',
        'cur': 'curie',
        'gal': 'galileo',
        'hop': 'hopper',
        'new': 'newton',
        'tes': 'tesla',
    }

    SUBDIV_TO_DIV = {
        'arc': 'cmp-arte',
        'cars': 'cmp-gaca',
        'carv': 'cmp-cuca',
        'cur': 'cmp-cuca',
        'gal': 'cmp-gaca',
        'hop': 'cmp-neho',
        'new': 'cmp-neho',
        'tes': 'cmp-arte',
    }

    def __init__(self, version):
        fms_api_secrets = Sitevar.get_by_id('fmsapi.secrets')
        if fms_api_secrets is None:
            raise Exception(
                "Missing sitevar: fmsapi.secrets. Can't access FMS API.")

        fms_api_username = fms_api_secrets.contents['username']
        fms_api_authkey = fms_api_secrets.contents['authkey']
        self._fms_api_authtoken = base64.b64encode('{}:{}'.format(
            fms_api_username, fms_api_authkey))

        self._is_down_sitevar = Sitevar.get_by_id('apistatus.fmsapi_down')
        if not self._is_down_sitevar:
            self._is_down_sitevar = Sitevar(id="apistatus.fmsapi_down",
                                            description="Is FMSAPI down?")

        if version == 'v1.0':
            FMS_API_URL_BASE = 'https://frc-api.firstinspires.org/api/v1.0'
            self.FMS_API_AWARDS_URL_PATTERN = FMS_API_URL_BASE + '/awards/%s/%s'  # (year, event_short)
            self.FMS_API_HYBRID_SCHEDULE_QUAL_URL_PATTERN = FMS_API_URL_BASE + '/schedule/%s/%s/qual/hybrid'  # (year, event_short)
            self.FMS_API_HYBRID_SCHEDULE_PLAYOFF_URL_PATTERN = FMS_API_URL_BASE + '/schedule/%s/%s/playoff/hybrid'  # (year, event_short)
            self.FMS_API_EVENT_RANKINGS_URL_PATTERN = FMS_API_URL_BASE + '/rankings/%s/%s'  # (year, event_short)
            self.FMS_API_EVENT_ALLIANCES_URL_PATTERN = FMS_API_URL_BASE + '/alliances/%s/%s'  # (year, event_short)
            self.FMS_API_TEAM_DETAILS_URL_PATTERN = FMS_API_URL_BASE + '/teams/%s/?teamNumber=%s'  # (year, teamNumber)
            self.FMS_API_EVENT_LIST_URL_PATTERN = FMS_API_URL_BASE + '/events/season=%s'
            self.FMS_API_EVENTTEAM_LIST_URL_PATTERN = FMS_API_URL_BASE + '/teams/?season=%s&eventCode=%s&page=%s'  # (year, eventCode, page)
        elif version == 'v2.0':
            FMS_API_URL_BASE = 'https://frc-api.firstinspires.org/v2.0'
            self.FMS_API_AWARDS_URL_PATTERN = FMS_API_URL_BASE + '/%s/awards/%s'  # (year, event_short)
            self.FMS_API_HYBRID_SCHEDULE_QUAL_URL_PATTERN = FMS_API_URL_BASE + '/%s/schedule/%s/qual/hybrid'  # (year, event_short)
            self.FMS_API_HYBRID_SCHEDULE_PLAYOFF_URL_PATTERN = FMS_API_URL_BASE + '/%s/schedule/%s/playoff/hybrid'  # (year, event_short)
            self.FMS_API_MATCH_DETAILS_QUAL_URL_PATTERN = FMS_API_URL_BASE + '/%s/scores/%s/qual'  # (year, event_short)
            self.FMS_API_MATCH_DETAILS_PLAYOFF_URL_PATTERN = FMS_API_URL_BASE + '/%s/scores/%s/playoff'  # (year, event_short)
            self.FMS_API_EVENT_RANKINGS_URL_PATTERN = FMS_API_URL_BASE + '/%s/rankings/%s'  # (year, event_short)
            self.FMS_API_EVENT_ALLIANCES_URL_PATTERN = FMS_API_URL_BASE + '/%s/alliances/%s'  # (year, event_short)
            self.FMS_API_TEAM_DETAILS_URL_PATTERN = FMS_API_URL_BASE + '/%s/teams/?teamNumber=%s'  # (year, teamNumber)
            self.FMS_API_EVENT_LIST_URL_PATTERN = FMS_API_URL_BASE + '/%s/events'  # year
            self.FMS_API_EVENTTEAM_LIST_URL_PATTERN = FMS_API_URL_BASE + '/%s/teams/?eventCode=%s&page=%s'  # (year, eventCode, page)
        else:
            raise Exception("Unknown FMS API version: {}".format(version))

    def _get_event_short(self, event_short):
        return self.EVENT_SHORT_EXCEPTIONS.get(event_short, event_short)

    @ndb.tasklet
    def _parse_async(self, url, parser):
        headers = {
            'Authorization': 'Basic {}'.format(self._fms_api_authtoken),
            'Cache-Control': 'no-cache, max-age=10',
            'Pragma': 'no-cache',
        }
        try:
            rpc = urlfetch.create_rpc(deadline=10)
            result = yield urlfetch.make_fetch_call(rpc, url, headers=headers)
        except Exception, e:
            logging.error("URLFetch failed for: {}".format(url))
            logging.info(e)
            raise ndb.Return(None)

        old_status = self._is_down_sitevar.contents
        if result.status_code == 200:
            self._is_down_sitevar.contents = False
            self._is_down_sitevar.put()
            ApiStatusController.clear_cache_if_needed(
                old_status, self._is_down_sitevar.contents)
            raise ndb.Return(parser.parse(json.loads(result.content)))
        elif result.status_code % 100 == 5:
            # 5XX error - something is wrong with the server
            logging.warning('URLFetch for %s failed; Error code %s' %
                            (url, result.status_code))
            self._is_down_sitevar.contents = True
            self._is_down_sitevar.put()
            ApiStatusController.clear_cache_if_needed(
                old_status, self._is_down_sitevar.contents)
            raise ndb.Return(None)
        else:
            logging.warning('URLFetch for %s failed; Error code %s' %
                            (url, result.status_code))
            raise ndb.Return(None)