From 7a598f65372b1b694d222946fd6269033bde0e54 Mon Sep 17 00:00:00 2001 From: emkael Date: Mon, 30 Dec 2019 12:49:54 +0100 Subject: New package structure for result info classes --- jfr_playoff/data/tournament/__init__.py | 58 +++++++++++++++++++++ jfr_playoff/data/tournament/jfrdb.py | 60 +++++++++++++++++++++ jfr_playoff/data/tournament/jfrhtml.py | 92 +++++++++++++++++++++++++++++++++ jfr_playoff/data/tournament/tcjson.py | 62 ++++++++++++++++++++++ 4 files changed, 272 insertions(+) create mode 100644 jfr_playoff/data/tournament/__init__.py create mode 100644 jfr_playoff/data/tournament/jfrdb.py create mode 100644 jfr_playoff/data/tournament/jfrhtml.py create mode 100644 jfr_playoff/data/tournament/tcjson.py (limited to 'jfr_playoff/data/tournament') diff --git a/jfr_playoff/data/tournament/__init__.py b/jfr_playoff/data/tournament/__init__.py new file mode 100644 index 0000000..e86d294 --- /dev/null +++ b/jfr_playoff/data/tournament/__init__.py @@ -0,0 +1,58 @@ +from jfr_playoff.logger import PlayoffLogger +from jfr_playoff.data.info import ResultInfo + + +class TournamentInfoClient(object): + def __init__(self, settings, database=None): + self.settings = settings + self.database = database + + def get_results_link(self, suffix): + pass + + def is_finished(self): + pass + + def get_tournament_results(self): + pass + + def get_exceptions(self, method): + pass + + +class TournamentInfo(ResultInfo): + def __init__(self, settings, database): + self.settings = settings + ResultInfo.__init__(self, settings, database) + + def fill_client_list(self, settings, database): + clients = [] + from jfr_playoff.data.tournament.jfrdb import JFRDbTournamentInfo + from jfr_playoff.data.tournament.jfrhtml import JFRHtmlTournamentInfo + from jfr_playoff.data.tournament.tcjson import TCJsonTournamentInfo + if (database is not None) and ('database' in settings): + clients.append(JFRDbTournamentInfo(settings, database)) + if 'link' in settings: + if settings['link'].endswith('leaderb.html'): + clients.append(JFRHtmlTournamentInfo(settings)) + clients.append(TCJsonTournamentInfo(settings)) + return clients + + def get_tournament_results(self): + teams = self.call_client('get_tournament_results', []) + if self.is_finished(): + final_positions = self.settings.get('final_positions', []) + PlayoffLogger.get('tournamentinfo').info( + 'setting final positions from tournament results: %s', + final_positions) + for position in final_positions: + if len(teams) >= position: + teams[position-1] = (teams[position-1] + [None] * 4)[0:4] + teams[position-1][3] = position + return teams + + def is_finished(self): + return self.call_client('is_finished', True) + + def get_results_link(self, suffix='leaderb.html'): + return self.call_client('get_results_link', None, suffix) diff --git a/jfr_playoff/data/tournament/jfrdb.py b/jfr_playoff/data/tournament/jfrdb.py new file mode 100644 index 0000000..48645df --- /dev/null +++ b/jfr_playoff/data/tournament/jfrdb.py @@ -0,0 +1,60 @@ +import jfr_playoff.sql as p_sql + +from jfr_playoff.logger import PlayoffLogger +from jfr_playoff.data.tournament import TournamentInfoClient + +SWISS_TIE_WARNING = 'tie detected in swiss %s.' + \ + ' Make sure to resolve the tie by arranging teams' + \ + ' in configuration file.' + + +class JFRDbTournamentInfo(TournamentInfoClient): + def get_exceptions(self, method): + return (IOError, TypeError, IndexError, KeyError) + + def get_results_link(self, suffix='leaderb.html'): + row = self.database.fetch( + self.settings['database'], p_sql.PREFIX, ()) + if row is not None: + if len(row) > 0: + link = row[0] + suffix + PlayoffLogger.get('tournament.jfrdb').info( + 'generating tournament-specific link from DB %s prefix: %s -> %s', + self.settings['database'], suffix, link) + return link + raise ValueError('unable to fetch db link') + + def is_finished(self): + finished = self.database.fetch( + self.settings['database'], p_sql.SWISS_ENDED, {}) + PlayoffLogger.get('tournament.jfrdb').info( + 'fetching tournament finished status from DB %s: %s', + self.settings['database'], finished) + return (len(finished) > 0) and (finished[0] > 0) + + def get_tournament_results(self): + if 'ties' not in self.settings: + self.settings['ties'] = [] + swiss_teams = self.database.fetch_all( + self.settings['database'], p_sql.SWISS_RESULTS, {}) + swiss_results = sorted( + swiss_teams, + key=lambda t: self.settings['ties'].index(t[0]) \ + if t[0] in self.settings['ties'] else -1) + swiss_results = sorted( + swiss_results, key=lambda t: t[1], reverse=True) + swiss_results = sorted(swiss_results, key=lambda team: team[2]) + PlayoffLogger.get('tournament.jfrdb').info( + 'fetched tournament results from database %s: %s', + self.settings['database'], swiss_results) + prev_result = None + for team in swiss_results: + if prev_result == team[1]: + PlayoffLogger.get('tournament.jfrdb').warning( + SWISS_TIE_WARNING, self.settings['database']) + prev_result = team[1] + db_teams = [[team[0], team[3], team[4]] for team in swiss_results] + PlayoffLogger.get('tournament.jfrdb').info( + 'fetched team list from database %s: %s', + self.settings['database'], db_teams) + return db_teams diff --git a/jfr_playoff/data/tournament/jfrhtml.py b/jfr_playoff/data/tournament/jfrhtml.py new file mode 100644 index 0000000..017ce3b --- /dev/null +++ b/jfr_playoff/data/tournament/jfrhtml.py @@ -0,0 +1,92 @@ +from math import ceil +import re + +from jfr_playoff.logger import PlayoffLogger +from jfr_playoff.remote import RemoteUrl as p_remote +from jfr_playoff.data.tournament import TournamentInfoClient + + +class JFRHtmlTournamentInfo(TournamentInfoClient): + def get_exceptions(self, method): + if method == 'get_results_link': + return (KeyError, ValueError) + return (TypeError, IndexError, KeyError, IOError, ValueError) + + def get_results_link(self, suffix='leaderb.html'): + link = re.sub(r'leaderb.html$', suffix, self.settings['link']) + PlayoffLogger.get('tournament.jfrhtml').info( + 'generating tournament-specific link from leaderboard link %s: %s -> %s', + self.settings['link'], suffix, link) + return link + + def is_finished(self): + PlayoffLogger.get('tournament.jfrhtml').info( + 'fetching tournament finished status from HTML: %s', + self.settings['link']) + leaderboard = p_remote.fetch(self.settings['link']) + leaderb_heading = leaderboard.select('td.bdnl12')[0].text + contains_digits = any(char.isdigit() for char in leaderb_heading) + PlayoffLogger.get('tournament.jfrhtml').info( + 'tournament header from HTML: %s, %s', + leaderb_heading, + 'contains digits' if contains_digits else "doesn't contain digits") + non_zero_scores = [ + imps.text + for imps + in leaderboard.select('td.bdc small') + if imps.text != '0-0'] + PlayoffLogger.get('tournament.jfrhtml').info( + 'tournament leaderboard from HTML: has %d non-zero scores', + len(non_zero_scores)) + finished = (not contains_digits) and (len(non_zero_scores) > 0) + PlayoffLogger.get('tournament.jfrhtml').info( + 'tournament leaderboard from HTML indicates finished: %s', + finished) + return finished + + def get_tournament_results(self): + PlayoffLogger.get('tournament.jfrhtml').info( + 'fetching tournament results from leaderboard URL: %s', + self.settings['link']) + leaderboard = p_remote.fetch(self.settings['link']) + result_links = [ + row.select('a[onmouseover]') + for row + in leaderboard.find_all('tr') + if len(row.select('a[onmouseover]')) > 0] + results = [None] * (len(result_links) * max([ + len(links) for links in result_links])) + for i in range(0, len(result_links)): + for j in range(0, len(result_links[i])): + results[len(result_links) * j + i] = result_links[i][j] + teams = [] + team_links = {} + for team in results: + if team is not None: + team_info = [] + fullname = team.text.strip(u'\xa0') + team_links[team['href']] = fullname + team_info.append(fullname) + team_info.append('') + team_image = team.find('img') + if team_image is not None: + team_info.append(team_image['src'].replace('images/', '')) + teams.append(team_info) + PlayoffLogger.get('tournament.jfrhtml').info( + 'read tournament results from leaderboard: %s', teams) + for table in range(1, int(ceil(len(teams)/2.0))+1): + table_url = self.get_results_link('1t%d-1.html' % (table)) + table_content = p_remote.fetch(table_url) + PlayoffLogger.get('tournament.jfrhtml').info( + 'reading team shortnames from traveller: %s', table_url) + for link in table_content.select('a.br'): + if link['href'] in team_links: + for team in teams: + if team[0] == team_links[link['href']]: + team[1] = link.text.strip(u'\xa0') + PlayoffLogger.get('tournament.jfrhtml').info( + 'shortname for %s: %s', team[0], team[1]) + break + PlayoffLogger.get('tournament.jfrhtml').info( + 'tournament results from HTML: %s', teams) + return teams diff --git a/jfr_playoff/data/tournament/tcjson.py b/jfr_playoff/data/tournament/tcjson.py new file mode 100644 index 0000000..5e375ed --- /dev/null +++ b/jfr_playoff/data/tournament/tcjson.py @@ -0,0 +1,62 @@ +import json +import urlparse + +from jfr_playoff.logger import PlayoffLogger +from jfr_playoff.remote import RemoteUrl as p_remote +from jfr_playoff.data.tournament import TournamentInfoClient + +FLAG_CDN_URL = 'https://cdn.tournamentcalculator.com/flags/' + + +class TCJsonTournamentInfo(TournamentInfoClient): + def get_exceptions(self, method): + return (TypeError, IndexError, KeyError, IOError, ValueError) + + def get_results_link(self, suffix): + link = urlparse.urljoin(self.settings['link'], suffix) + PlayoffLogger.get('tournament.tcjson').info( + 'generating tournament-specific link from leaderboard link %s: %s -> %s', + self.settings['link'], suffix, link) + return link + + def is_finished(self): + settings_json = json.loads( + p_remote.fetch_raw(self.get_results_link('settings.json'))) + live_results = settings_json['LiveResults'] + last_round = settings_json['LastPlayedRound'] + last_session = settings_json['LastPlayedSession'] + finished = (not live_results) \ + and (last_round > 0) and (last_session > 0) + PlayoffLogger.get('tournament.tcjson').info( + 'tournament settings (live = %s, last_round = %d, last_session = %d) indicate finished: %s', + live_results, last_round, last_session, finished) + return finished + + def get_tournament_results(self): + results = [] + results_json = json.loads( + p_remote.fetch_raw(self.get_results_link('results.json'))) + participant_groups = [] + for result in results_json['Results']: + group = result['ParticipantGroup'] + if group is not None: + if group not in participant_groups: + participant_groups.append(group) + group_id = participant_groups.index(group) + 1 + else: + group_id = 999999 + participant = result['Participant'] + flag_url = None + flag = participant['_flag'] + if flag is not None: + flag_url = self.get_results_link( + flag['CustomFlagPath'] + if flag['IsCustom'] + else '%s/%s.png' % (FLAG_CDN_URL, flag['CountryNameCode'])) + results.append(( + group_id, result['Place'], + participant['_name'], participant['_shortName'], + flag_url)) + PlayoffLogger.get('tournament.tcjson').info( + 'tournament results fetched: %s' % results) + return [list(r[2:]) + [None] for r in sorted(results)] -- cgit v1.2.3