From 7a598f65372b1b694d222946fd6269033bde0e54 Mon Sep 17 00:00:00 2001
From: emkael <emkael@tlen.pl>
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