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.py | 237 -------------------------------- jfr_playoff/data/__init__.py | 207 ++++++++++++++++++++++++++++ jfr_playoff/data/info.py | 29 ++++ 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 +++++++++ jfr_playoff/matchinfo.py | 5 +- jfr_playoff/tournamentinfo/__init__.py | 58 -------- jfr_playoff/tournamentinfo/jfrdb.py | 60 -------- jfr_playoff/tournamentinfo/jfrhtml.py | 92 ------------- jfr_playoff/tournamentinfo/tcjson.py | 62 --------- 12 files changed, 511 insertions(+), 511 deletions(-) delete mode 100644 jfr_playoff/data.py create mode 100644 jfr_playoff/data/__init__.py create mode 100644 jfr_playoff/data/info.py 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 delete mode 100644 jfr_playoff/tournamentinfo/__init__.py delete mode 100644 jfr_playoff/tournamentinfo/jfrdb.py delete mode 100644 jfr_playoff/tournamentinfo/jfrhtml.py delete mode 100644 jfr_playoff/tournamentinfo/tcjson.py (limited to 'jfr_playoff') diff --git a/jfr_playoff/data.py b/jfr_playoff/data.py deleted file mode 100644 index 132ee02..0000000 --- a/jfr_playoff/data.py +++ /dev/null @@ -1,237 +0,0 @@ -from jfr_playoff.logger import PlayoffLogger - - -class ResultInfo(object): - def __init__(self, *args): - self.clients = self.fill_client_list(*args) - - def fill_client_list(self, settings, database): - return [] - - def call_client(self, method, default, *args): - PlayoffLogger.get('resultinfo').info( - 'calling %s on result info clients', method) - for client in self.clients: - try: - ret = getattr(client, method)(*args) - PlayoffLogger.get('resultinfo').info( - '%s method returned %s', method, ret) - return ret - except Exception as e: - if type(e) in client.get_exceptions(method): - PlayoffLogger.get('resultinfo').warning( - '%s method raised %s(%s)', - method, type(e).__name__, str(e)) - else: - raise - PlayoffLogger.get('resultinfo').info( - '%s method returning default: %s', method, default) - return default - - -from cached_property import cached_property - -from jfr_playoff.db import PlayoffDB -from jfr_playoff.dto import Phase -from jfr_playoff.matchinfo import MatchInfo -from jfr_playoff.tournamentinfo import TournamentInfo - - -class PlayoffData(object): - def __init__(self, settings=None): - if settings is not None: - self.database = PlayoffDB(settings.get('database')) \ - if settings.has_section('database') else None - if self.database is None: - PlayoffLogger.get('db').warning( - PlayoffDB.DATABASE_NOT_CONFIGURED_WARNING) - self.team_settings = settings.get('teams') - self.custom_final_order = [] - if settings.has_section('custom_final_order'): - self.custom_final_order = settings.get('custom_final_order') - self.custom_final_order = [ - t for t in [ - self.teams[i-1] if isinstance(i, int) - else self.get_team_data_by_name(i) - for i in self.custom_final_order] - if t is not None] - self.phases = settings.get('phases') - self.swiss = [] - if settings.has_section('swiss'): - self.swiss = settings.get('swiss') - self.aliases = {} - if settings.has_section('team_aliases'): - self.aliases = settings.get('team_aliases') - self.grid = [] - self.match_info = {} - self.leaderboard = [] - - def fetch_team_list(self, settings, db_interface): - if isinstance(settings, list): - PlayoffLogger.get('data').info( - 'team list pre-defined: %s', settings) - return settings - tournament_info = TournamentInfo(settings, db_interface) - team_list = tournament_info.get_tournament_results() - if len(team_list) == 0: - PlayoffLogger.get('data').warning('team list is empty!') - return team_list if 'max_teams' not in settings \ - else team_list[0:settings['max_teams']] - - @cached_property - def teams(self): - return self.fetch_team_list(self.team_settings, self.database) - - def generate_phases(self): - self.grid = [] - for phase in self.phases: - dummies = phase.get('dummies', []) - phase_count = len(phase['matches']) + len(dummies) - phase_object = Phase() - phase_object.title = phase['title'] - phase_object.link = phase.get('link', None) - phase_object.matches = [None] * phase_count - phase_pos = 0 - for match in phase['matches']: - while phase_pos in dummies: - phase_pos += 1 - phase_object.matches[phase_pos] = match['id'] - phase_pos += 1 - PlayoffLogger.get('data').info('phase object: %s', phase_object) - self.grid.append(phase_object) - return self.grid - - def fill_match_info(self): - self.match_info = {} - for phase in self.phases: - for match in phase['matches']: - match_info = MatchInfo(match, self.teams, self.database, self.aliases) - if 'link' in phase: - match_info.set_phase_link(phase['link']) - self.match_info[match['id']] = match_info.get_info() - if self.match_info[match['id']].running > 0: - for phase_obj in self.grid: - if match['id'] in phase_obj.matches: - phase_obj.running = True - PlayoffLogger.get('data').info( - 'match object: %s', self.match_info[match['id']]) - return self.match_info - - def get_swiss_link(self, event): - event_info = TournamentInfo(event, self.database) - swiss_link = event_info.get_results_link() - if event.get('relative_path', None): - swiss_link = '%s/%s' % (event['relative_path'], swiss_link) - PlayoffLogger.get('data').info('swiss link: %s', swiss_link) - return swiss_link - - def prefill_leaderboard(self, teams): - self.leaderboard = [None] * len(teams) - for team in teams: - if len(team) > 3 and team[3] is not None: - self.leaderboard[team[3]-1] = team[0] - self.fill_swiss_leaderboard(self.swiss, teams) - PlayoffLogger.get('data').info('leaderboard pre-filled: %s', self.leaderboard) - return self.leaderboard - - def fill_swiss_leaderboard(self, swiss, teams): - teams = [team[0] for team in teams] - for event in swiss: - event['ties'] = teams - event_info = TournamentInfo(event, self.database) - if event_info.is_finished(): - swiss_position = event.get('swiss_position', 1) - position_limit = event.get('position_to', 9999) - place = 1 - swiss_results = event_info.get_tournament_results() - for team in swiss_results: - if place >= swiss_position: - target_position = event['position'] \ - + place - swiss_position - if target_position <= min( - position_limit, len(self.leaderboard)): - self.leaderboard[ - target_position - 1] = team[0] - place += 1 - PlayoffLogger.get('data').info( - 'leaderboard after %s swiss: %s', event, self.leaderboard) - - def fill_leaderboard(self): - self.prefill_leaderboard(self.teams) - leaderboard_teams = {} - for phase in self.phases: - for match in phase['matches']: - if 'winner' in match: - winner_key = tuple(match['winner']) - if winner_key not in leaderboard_teams: - leaderboard_teams[winner_key] = [] - leaderboard_teams[winner_key].append( - self.match_info[match['id']].winner) - if 'loser' in match: - loser_key = tuple(match['loser']) - if loser_key not in leaderboard_teams: - leaderboard_teams[loser_key] = [] - leaderboard_teams[loser_key].append( - self.match_info[match['id']].loser) - final_order = self.custom_final_order + [ - t for t in self.teams if t not in self.custom_final_order] - PlayoffLogger.get('data').info( - 'custom order for final positions: %s', self.custom_final_order) - PlayoffLogger.get('data').info( - 'order of teams to fill leaderboard positions: %s', - final_order) - for positions, position_teams in leaderboard_teams.iteritems(): - positions = list(positions) - PlayoffLogger.get('data').info( - 'filling leaderboard positions %s with teams %s', - positions, position_teams) - if len(positions) == len([ - team for team in position_teams if team is not None]): - for table_team in final_order: - if table_team[0] in position_teams: - position = positions.pop(0) - self.leaderboard[position-1] = table_team[0] - PlayoffLogger.get('data').info( - 'team %s in position %d', table_team[0], position) - PlayoffLogger.get('data').info( - 'leaderboard filled: %s', self.leaderboard) - return self.leaderboard - - def get_swiss_info(self): - swiss_info = [{ - 'link': self.get_swiss_link(event), - 'position': event['position'], - 'label': event.get('label', None), - 'finished': TournamentInfo(event, self.database).is_finished() - } for event in self.swiss] - PlayoffLogger.get('data').info('swiss info: %s', swiss_info) - return swiss_info - - def get_dimensions(self): - dimensions = ( - len(self.phases), - max([ - len(phase['matches']) + len(phase.get('dummies', [])) - for phase in self.phases - ] or [0]) - ) - PlayoffLogger.get('data').info('grid dimensions: %s', dimensions) - return dimensions - - def get_team_data_by_name(self, fullname): - for team in self.teams: - if team[0] == fullname: - return team - return None - - def get_shortname(self, fullname): - for team in self.teams: - if team[0] == fullname: - return team[1] - return fullname - - def get_team_image(self, fullname): - for team in self.teams: - if team[0] == fullname and len(team) > 2: - return team[2] - return None diff --git a/jfr_playoff/data/__init__.py b/jfr_playoff/data/__init__.py new file mode 100644 index 0000000..494eaef --- /dev/null +++ b/jfr_playoff/data/__init__.py @@ -0,0 +1,207 @@ +from cached_property import cached_property + +from jfr_playoff.db import PlayoffDB +from jfr_playoff.dto import Phase +from jfr_playoff.matchinfo import MatchInfo +from jfr_playoff.data.tournament import TournamentInfo +from jfr_playoff.logger import PlayoffLogger + + +class PlayoffData(object): + def __init__(self, settings=None): + if settings is not None: + self.database = PlayoffDB(settings.get('database')) \ + if settings.has_section('database') else None + if self.database is None: + PlayoffLogger.get('db').warning( + PlayoffDB.DATABASE_NOT_CONFIGURED_WARNING) + self.team_settings = settings.get('teams') + self.custom_final_order = [] + if settings.has_section('custom_final_order'): + self.custom_final_order = settings.get('custom_final_order') + self.custom_final_order = [ + t for t in [ + self.teams[i-1] if isinstance(i, int) + else self.get_team_data_by_name(i) + for i in self.custom_final_order] + if t is not None] + self.phases = settings.get('phases') + self.swiss = [] + if settings.has_section('swiss'): + self.swiss = settings.get('swiss') + self.aliases = {} + if settings.has_section('team_aliases'): + self.aliases = settings.get('team_aliases') + self.grid = [] + self.match_info = {} + self.leaderboard = [] + + def fetch_team_list(self, settings, db_interface): + if isinstance(settings, list): + PlayoffLogger.get('data').info( + 'team list pre-defined: %s', settings) + return settings + tournament_info = TournamentInfo(settings, db_interface) + team_list = tournament_info.get_tournament_results() + if len(team_list) == 0: + PlayoffLogger.get('data').warning('team list is empty!') + return team_list if 'max_teams' not in settings \ + else team_list[0:settings['max_teams']] + + @cached_property + def teams(self): + return self.fetch_team_list(self.team_settings, self.database) + + def generate_phases(self): + self.grid = [] + for phase in self.phases: + dummies = phase.get('dummies', []) + phase_count = len(phase['matches']) + len(dummies) + phase_object = Phase() + phase_object.title = phase['title'] + phase_object.link = phase.get('link', None) + phase_object.matches = [None] * phase_count + phase_pos = 0 + for match in phase['matches']: + while phase_pos in dummies: + phase_pos += 1 + phase_object.matches[phase_pos] = match['id'] + phase_pos += 1 + PlayoffLogger.get('data').info('phase object: %s', phase_object) + self.grid.append(phase_object) + return self.grid + + def fill_match_info(self): + self.match_info = {} + for phase in self.phases: + for match in phase['matches']: + match_info = MatchInfo(match, self.teams, self.database, self.aliases) + if 'link' in phase: + match_info.set_phase_link(phase['link']) + self.match_info[match['id']] = match_info.get_info() + if self.match_info[match['id']].running > 0: + for phase_obj in self.grid: + if match['id'] in phase_obj.matches: + phase_obj.running = True + PlayoffLogger.get('data').info( + 'match object: %s', self.match_info[match['id']]) + return self.match_info + + def get_swiss_link(self, event): + event_info = TournamentInfo(event, self.database) + swiss_link = event_info.get_results_link() + if event.get('relative_path', None): + swiss_link = '%s/%s' % (event['relative_path'], swiss_link) + PlayoffLogger.get('data').info('swiss link: %s', swiss_link) + return swiss_link + + def prefill_leaderboard(self, teams): + self.leaderboard = [None] * len(teams) + for team in teams: + if len(team) > 3 and team[3] is not None: + self.leaderboard[team[3]-1] = team[0] + self.fill_swiss_leaderboard(self.swiss, teams) + PlayoffLogger.get('data').info('leaderboard pre-filled: %s', self.leaderboard) + return self.leaderboard + + def fill_swiss_leaderboard(self, swiss, teams): + teams = [team[0] for team in teams] + for event in swiss: + event['ties'] = teams + event_info = TournamentInfo(event, self.database) + if event_info.is_finished(): + swiss_position = event.get('swiss_position', 1) + position_limit = event.get('position_to', 9999) + place = 1 + swiss_results = event_info.get_tournament_results() + for team in swiss_results: + if place >= swiss_position: + target_position = event['position'] \ + + place - swiss_position + if target_position <= min( + position_limit, len(self.leaderboard)): + self.leaderboard[ + target_position - 1] = team[0] + place += 1 + PlayoffLogger.get('data').info( + 'leaderboard after %s swiss: %s', event, self.leaderboard) + + def fill_leaderboard(self): + self.prefill_leaderboard(self.teams) + leaderboard_teams = {} + for phase in self.phases: + for match in phase['matches']: + if 'winner' in match: + winner_key = tuple(match['winner']) + if winner_key not in leaderboard_teams: + leaderboard_teams[winner_key] = [] + leaderboard_teams[winner_key].append( + self.match_info[match['id']].winner) + if 'loser' in match: + loser_key = tuple(match['loser']) + if loser_key not in leaderboard_teams: + leaderboard_teams[loser_key] = [] + leaderboard_teams[loser_key].append( + self.match_info[match['id']].loser) + final_order = self.custom_final_order + [ + t for t in self.teams if t not in self.custom_final_order] + PlayoffLogger.get('data').info( + 'custom order for final positions: %s', self.custom_final_order) + PlayoffLogger.get('data').info( + 'order of teams to fill leaderboard positions: %s', + final_order) + for positions, position_teams in leaderboard_teams.iteritems(): + positions = list(positions) + PlayoffLogger.get('data').info( + 'filling leaderboard positions %s with teams %s', + positions, position_teams) + if len(positions) == len([ + team for team in position_teams if team is not None]): + for table_team in final_order: + if table_team[0] in position_teams: + position = positions.pop(0) + self.leaderboard[position-1] = table_team[0] + PlayoffLogger.get('data').info( + 'team %s in position %d', table_team[0], position) + PlayoffLogger.get('data').info( + 'leaderboard filled: %s', self.leaderboard) + return self.leaderboard + + def get_swiss_info(self): + swiss_info = [{ + 'link': self.get_swiss_link(event), + 'position': event['position'], + 'label': event.get('label', None), + 'finished': TournamentInfo(event, self.database).is_finished() + } for event in self.swiss] + PlayoffLogger.get('data').info('swiss info: %s', swiss_info) + return swiss_info + + def get_dimensions(self): + dimensions = ( + len(self.phases), + max([ + len(phase['matches']) + len(phase.get('dummies', [])) + for phase in self.phases + ] or [0]) + ) + PlayoffLogger.get('data').info('grid dimensions: %s', dimensions) + return dimensions + + def get_team_data_by_name(self, fullname): + for team in self.teams: + if team[0] == fullname: + return team + return None + + def get_shortname(self, fullname): + for team in self.teams: + if team[0] == fullname: + return team[1] + return fullname + + def get_team_image(self, fullname): + for team in self.teams: + if team[0] == fullname and len(team) > 2: + return team[2] + return None diff --git a/jfr_playoff/data/info.py b/jfr_playoff/data/info.py new file mode 100644 index 0000000..371ac6a --- /dev/null +++ b/jfr_playoff/data/info.py @@ -0,0 +1,29 @@ +from jfr_playoff.logger import PlayoffLogger + + +class ResultInfo(object): + def __init__(self, *args): + self.clients = self.fill_client_list(*args) + + def fill_client_list(self, settings, database): + return [] + + def call_client(self, method, default, *args): + PlayoffLogger.get('resultinfo').info( + 'calling %s on result info clients', method) + for client in self.clients: + try: + ret = getattr(client, method)(*args) + PlayoffLogger.get('resultinfo').info( + '%s method returned %s', method, ret) + return ret + except Exception as e: + if type(e) in client.get_exceptions(method): + PlayoffLogger.get('resultinfo').warning( + '%s method raised %s(%s)', + method, type(e).__name__, str(e)) + else: + raise + PlayoffLogger.get('resultinfo').info( + '%s method returning default: %s', method, default) + return default 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)] diff --git a/jfr_playoff/matchinfo.py b/jfr_playoff/matchinfo.py index 9d48b57..1c0bcd8 100644 --- a/jfr_playoff/matchinfo.py +++ b/jfr_playoff/matchinfo.py @@ -4,10 +4,11 @@ from urlparse import urljoin import jfr_playoff.sql as p_sql from jfr_playoff.dto import Match, Team from jfr_playoff.remote import RemoteUrl as p_remote -from jfr_playoff.data import ResultInfo -from jfr_playoff.tournamentinfo import TournamentInfo +from jfr_playoff.data.info import ResultInfo +from jfr_playoff.data.tournament import TournamentInfo from jfr_playoff.logger import PlayoffLogger + class MatchInfo(ResultInfo): matches = {} diff --git a/jfr_playoff/tournamentinfo/__init__.py b/jfr_playoff/tournamentinfo/__init__.py deleted file mode 100644 index 21cd62a..0000000 --- a/jfr_playoff/tournamentinfo/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -from jfr_playoff.logger import PlayoffLogger -from jfr_playoff.data 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.tournamentinfo.jfrdb import JFRDbTournamentInfo - from jfr_playoff.tournamentinfo.jfrhtml import JFRHtmlTournamentInfo - from jfr_playoff.tournamentinfo.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/tournamentinfo/jfrdb.py b/jfr_playoff/tournamentinfo/jfrdb.py deleted file mode 100644 index 0f39b47..0000000 --- a/jfr_playoff/tournamentinfo/jfrdb.py +++ /dev/null @@ -1,60 +0,0 @@ -import jfr_playoff.sql as p_sql - -from jfr_playoff.logger import PlayoffLogger -from jfr_playoff.tournamentinfo 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/tournamentinfo/jfrhtml.py b/jfr_playoff/tournamentinfo/jfrhtml.py deleted file mode 100644 index 8f40c55..0000000 --- a/jfr_playoff/tournamentinfo/jfrhtml.py +++ /dev/null @@ -1,92 +0,0 @@ -from math import ceil -import re - -from jfr_playoff.logger import PlayoffLogger -from jfr_playoff.remote import RemoteUrl as p_remote -from jfr_playoff.tournamentinfo 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/tournamentinfo/tcjson.py b/jfr_playoff/tournamentinfo/tcjson.py deleted file mode 100644 index 5a81ef3..0000000 --- a/jfr_playoff/tournamentinfo/tcjson.py +++ /dev/null @@ -1,62 +0,0 @@ -import json -import urlparse - -from jfr_playoff.logger import PlayoffLogger -from jfr_playoff.remote import RemoteUrl as p_remote -from jfr_playoff.tournamentinfo 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