summaryrefslogtreecommitdiff
path: root/jfr_playoff
diff options
context:
space:
mode:
authoremkael <emkael@tlen.pl>2017-10-02 23:48:02 +0200
committeremkael <emkael@tlen.pl>2017-10-02 23:48:02 +0200
commit19caef32a560a4a797fba4ab2fa93bc2b410039b (patch)
tree6b965e9e3320305517929d4792ec1948c51ec2a4 /jfr_playoff
parent86654dee683d9a2cda00a528b006433cebeebf26 (diff)
parent40d09a61727d3c960a816def07fb319853a90116 (diff)
Merge branch 'refactor'
Diffstat (limited to 'jfr_playoff')
-rw-r--r--jfr_playoff/__init__.py0
-rw-r--r--jfr_playoff/data.py203
-rw-r--r--jfr_playoff/db.py22
-rw-r--r--jfr_playoff/dto.py23
-rw-r--r--jfr_playoff/filemanager.py53
-rw-r--r--jfr_playoff/generator.py126
-rw-r--r--jfr_playoff/settings.py41
-rw-r--r--jfr_playoff/sql.py36
-rw-r--r--jfr_playoff/template.py128
9 files changed, 632 insertions, 0 deletions
diff --git a/jfr_playoff/__init__.py b/jfr_playoff/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/jfr_playoff/__init__.py
diff --git a/jfr_playoff/data.py b/jfr_playoff/data.py
new file mode 100644
index 0000000..6fc3c4b
--- /dev/null
+++ b/jfr_playoff/data.py
@@ -0,0 +1,203 @@
+from urlparse import urljoin
+
+import mysql
+
+import jfr_playoff.sql as p_sql
+from jfr_playoff.db import PlayoffDB
+from jfr_playoff.dto import Match, Phase, Team
+
+
+class PlayoffData(object):
+ def __init__(self, settings):
+ self.database = PlayoffDB(settings.get('database'))
+ self.phases = settings.get('phases')
+ self.teams = settings.get('teams')
+ self.grid = []
+ self.match_info = {}
+ self.leaderboard = []
+
+ def generate_phases(self):
+ self.grid = []
+ for phase in self.phases:
+ phase_count = len(phase['matches'])
+ if 'dummies' in phase:
+ phase_count += len(phase['dummies'])
+ phase_object = Phase()
+ phase_object.title = phase['title']
+ phase_object.link = phase['link']
+ phase_object.matches = [None] * phase_count
+ phase_pos = 0
+ for match in phase['matches']:
+ if 'dummies' in phase:
+ while phase_pos in phase['dummies']:
+ phase_pos += 1
+ phase_object.matches[phase_pos] = match['id']
+ phase_pos += 1
+ 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']:
+ self.match_info[match['id']] = self.get_match_info(match)
+ 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
+ if self.match_info[match['id']].link is None:
+ self.match_info[match['id']].link = phase['link']
+ else:
+ self.match_info[match['id']].link = urljoin(
+ phase['link'], self.match_info[match['id']].link)
+ return self.match_info
+
+ def get_match_link(self, match):
+ try:
+ row = self.database.fetch(match['database'], p_sql.PREFIX, ())
+ if row is not None:
+ if len(row) > 0:
+ return '%srunda%d.html' % (row[0], match['round'])
+ except mysql.connector.Error:
+ return None
+ return None
+
+ def get_db_match_teams(self, match):
+ teams = [Team(), Team()]
+ row = self.database.fetch(
+ match['database'], p_sql.MATCH_RESULTS,
+ (match['table'], match['round']))
+ teams[0].name = row[0]
+ teams[1].name = row[1]
+ teams[0].score = row[3] + row[5]
+ teams[1].score = row[4] + row[6]
+ if row[2] > 0:
+ teams[0].score += row[2]
+ else:
+ teams[1].score -= row[2]
+ return teams
+
+ def get_config_match_teams(self, match):
+ teams = [Team(), Team()]
+ for i in range(0, 2):
+ if isinstance(match['teams'][i], basestring):
+ teams[i].name = match['teams'][i]
+ elif isinstance(match['teams'][i], list):
+ teams[i].name = '<br />'.join(match['teams'][i])
+ else:
+ match_teams = []
+ if 'winner' in match['teams'][i]:
+ match_teams += [
+ self.match_info[winner_match].winner
+ for winner_match in match['teams'][i]['winner']]
+ if 'loser' in match['teams'][i]:
+ match_teams += [
+ self.match_info[loser_match].loser
+ for loser_match in match['teams'][i]['loser']]
+ if 'place' in match['teams'][i]:
+ match_teams += [
+ self.teams[place-1][0]
+ for place in match['teams'][i]['place']]
+ known_teams = [team for team in match_teams if team is not None]
+ if len(known_teams) > 0:
+ teams[i].name = '<br />'.join([
+ team if team is not None
+ else '??' for team in match_teams])
+ else:
+ teams[i].name = ''
+ return teams
+
+ def get_match_info(self, match):
+ info = Match()
+ info.id = match['id']
+ info.winner_matches = []
+ info.loser_matches = []
+ for i in range(0, 2):
+ if 'winner' in match['teams'][i]:
+ info.winner_matches += match['teams'][i]['winner']
+ if 'loser' in match['teams'][i]:
+ info.loser_matches += match['teams'][i]['loser']
+ info.winner_matches = list(set(info.winner_matches))
+ info.loser_matches = list(set(info.loser_matches))
+ info.link = self.get_match_link(match)
+ try:
+ info.teams = self.get_db_match_teams(match)
+ except (mysql.connector.Error, TypeError, IndexError):
+ info.teams = self.get_config_match_teams(match)
+ try:
+ towels = self.database.fetch(
+ match['database'], p_sql.TOWEL_COUNT,
+ (match['table'], match['round']))
+ row = [0 if r is None
+ else r for r in
+ self.database.fetch(
+ match['database'], p_sql.BOARD_COUNT,
+ (match['table'], match['round']))]
+ if row[1] > 0:
+ info.running = int(row[1])
+ if row[1] >= row[0] - towels[0]:
+ info.running = 0
+ if info.teams[0].score > info.teams[1].score:
+ info.winner = info.teams[0].name
+ info.loser = info.teams[1].name
+ else:
+ info.loser = info.teams[0].name
+ info.winner = info.teams[1].name
+ except (mysql.connector.Error, TypeError, KeyError):
+ pass
+ return info
+
+ def prefill_leaderboard(self, teams):
+ self.leaderboard = [None] * len(teams)
+ for team in teams:
+ if len(team) > 3:
+ self.leaderboard[team[3]-1] = team[0]
+ return 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)
+ for positions, position_teams in leaderboard_teams.iteritems():
+ positions = list(positions)
+ if len(positions) == len([
+ team for team in position_teams if team is not None]):
+ for table_team in self.teams:
+ if table_team[0] in position_teams:
+ position = positions.pop(0)
+ self.leaderboard[position-1] = table_team[0]
+ return self.leaderboard
+
+ def get_dimensions(self):
+ return (
+ len(self.phases),
+ max([
+ len(phase['matches']) + len(phase['dummies'])
+ if 'dummies' in phase
+ else len(phase['matches'])
+ for phase in self.phases]))
+
+ 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/db.py b/jfr_playoff/db.py
new file mode 100644
index 0000000..b94f3d5
--- /dev/null
+++ b/jfr_playoff/db.py
@@ -0,0 +1,22 @@
+import mysql.connector
+
+
+class PlayoffDB(object):
+
+ db_cursor = None
+
+ def __init__(self, settings):
+ self.database = mysql.connector.connect(
+ user=settings['user'],
+ password=settings['pass'],
+ host=settings['host'],
+ port=settings['port'])
+ self.db_cursor = self.database.cursor(buffered=True)
+
+ def get_cursor(self):
+ return self.db_cursor
+
+ def fetch(self, db_name, sql, params):
+ self.db_cursor.execute(sql.replace('#db#', db_name), params)
+ row = self.db_cursor.fetchone()
+ return row
diff --git a/jfr_playoff/dto.py b/jfr_playoff/dto.py
new file mode 100644
index 0000000..f5e08ef
--- /dev/null
+++ b/jfr_playoff/dto.py
@@ -0,0 +1,23 @@
+class Team(object):
+ name = ''
+ score = 0.0
+
+
+class Match(object):
+ id = None
+ teams = None
+ running = 0
+ link = None
+ winner = None
+ loser = None
+ winner_matches = None
+ loser_matches = None
+
+
+class Phase(object):
+ title = None
+ link = None
+ matches = []
+ running = False
+
+__all__ = ('Team', 'Match', 'Phase')
diff --git a/jfr_playoff/filemanager.py b/jfr_playoff/filemanager.py
new file mode 100644
index 0000000..5cd2a80
--- /dev/null
+++ b/jfr_playoff/filemanager.py
@@ -0,0 +1,53 @@
+import os
+import shutil
+import socket
+
+import __main__
+
+
+class PlayoffFileManager(object):
+
+ def __init__(self, settings):
+ self.goniec = settings.get('goniec')
+ self.output_file = settings.get('output')
+ self.output_path = os.path.dirname(
+ self.output_file
+ ).strip(os.sep) + os.sep
+ self.files = set()
+
+ def reset(self):
+ self.files.clear()
+
+ def register_file(self, path):
+ if path.startswith(self.output_path):
+ self.files.add(path.replace(self.output_path, ''))
+
+ def write_content(self, content):
+ output = open(self.output_file, 'w')
+ output.write(content.encode('utf8'))
+ output.close()
+ self.register_file(self.output_file)
+ return self.output_file
+
+ def copy_scripts(self, script_path='sklady/playoff.js'):
+ script_output_path = os.path.join(self.output_path, script_path)
+ shutil.copy(
+ unicode(os.path.join(
+ os.path.dirname(__main__.__file__), 'playoff.js')),
+ unicode(script_output_path))
+ self.register_file(script_output_path)
+ return script_output_path
+
+ def send_files(self):
+ if self.goniec['enabled']:
+ try:
+ content_lines = [self.output_path] + \
+ list(self.files) + \
+ ['bye', '']
+ print '\n'.join(content_lines)
+ goniec = socket.socket()
+ goniec.connect((self.goniec['host'], self.goniec['port']))
+ goniec.sendall('\n'.join(content_lines))
+ goniec.close()
+ except socket.error:
+ pass
diff --git a/jfr_playoff/generator.py b/jfr_playoff/generator.py
new file mode 100644
index 0000000..5868750
--- /dev/null
+++ b/jfr_playoff/generator.py
@@ -0,0 +1,126 @@
+from datetime import datetime
+
+import jfr_playoff.template as p_temp
+from jfr_playoff.data import PlayoffData
+
+
+class PlayoffGenerator(object):
+ def __init__(self, settings):
+ self.data = PlayoffData(settings)
+ self.page = settings.get('page')
+ self.canvas = {}
+ if settings.has_section('canvas'):
+ self.canvas = settings.get('canvas')
+
+ def generate_content(self):
+ return p_temp.PAGE % (
+ p_temp.PAGE_HEAD % (
+ p_temp.PAGE_HEAD_REFRESH % (
+ self.page['refresh'])
+ if self.page['refresh'] > 0 else '',
+ self.page['title']),
+ p_temp.PAGE_BODY % (
+ self.page['logoh'],
+ self.get_match_grid(
+ self.data.get_dimensions(),
+ self.data.generate_phases(),
+ self.data.fill_match_info()),
+ self.get_leaderboard_table(self.data.fill_leaderboard()),
+ p_temp.PAGE_BODY_FOOTER.decode('utf8') % (
+ datetime.now().strftime('%Y-%m-%d o %H:%M'))))
+
+ def get_match_table(self, match):
+ rows = ''
+ for team in match.teams:
+ rows += p_temp.MATCH_TEAM_ROW % (
+ ' '.join([
+ 'winner' if team.name == match.winner else '',
+ 'loser' if team.name == match.loser else ''
+ ]).strip(),
+ match.link,
+ team.name,
+ ' / '.join([
+ self.data.get_shortname(name) for name in
+ team.name.split('<br />')]),
+ match.link,
+ team.score)
+ html = p_temp.MATCH_TABLE.decode('utf8') % (
+ int(self.page['width'] * 0.75),
+ int(self.page['width'] * 0.25),
+ rows)
+ if match.running > 0:
+ html += p_temp.MATCH_RUNNING % (match.link, match.running)
+ return html
+
+ def get_phase_header(self, phase, position):
+ if phase.running:
+ grid_header = p_temp.MATCH_GRID_PHASE_RUNNING
+ else:
+ grid_header = p_temp.MATCH_GRID_PHASE
+ return grid_header % (
+ phase.link,
+ self.page['width'],
+ position,
+ phase.title)
+
+ def get_match_box(self, match, position):
+ if match is not None:
+ return p_temp.MATCH_BOX % (
+ position[0], position[1],
+ match.id,
+ ' '.join([
+ str(m) for m in match.winner_matches
+ ]) if match.winner_matches is not None else '',
+ ' '.join([
+ str(m) for m in match.loser_matches
+ ]) if match.loser_matches is not None else '',
+ self.get_match_table(match))
+ return ''
+
+ def get_match_grid(self, dimensions, grid, matches):
+ canvas_size = (
+ dimensions[0] * (
+ self.page['width'] + self.page['margin']
+ ) - self.page['margin'],
+ dimensions[1] * (
+ self.page['height'] + self.page['margin']
+ ) - self.page['margin'])
+ grid_boxes = ''
+ col_no = 0
+ for phase in grid:
+ grid_x = col_no * (self.page['width'] + self.page['margin'])
+ grid_boxes += self.get_phase_header(phase, grid_x)
+ match_height = canvas_size[1] / len(phase.matches)
+ row_no = 0
+ for match in phase.matches:
+ grid_y = int(row_no * match_height +
+ 0.5 * (match_height - self.page['height']))
+ grid_boxes += self.get_match_box(
+ matches[match] if match is not None else None,
+ (grid_x, grid_y))
+ row_no += 1
+ col_no += 1
+ return p_temp.MATCH_GRID % (
+ canvas_size[0], canvas_size[1],
+ canvas_size[0], canvas_size[1],
+ ' '.join(['data-%s="%s"' % (
+ setting.replace('_', '-'), str(value)
+ ) for setting, value in self.canvas.iteritems()]),
+ grid_boxes
+ )
+
+ def get_leaderboard_table(self, leaderboard):
+ if len([t for t in leaderboard if t is not None]) == 0:
+ return ''
+ position = 1
+ rows = ''
+ for team in leaderboard:
+ rows += p_temp.LEADERBOARD_ROW % (
+ position, self.get_flag(team), team or '')
+ position += 1
+ html = p_temp.LEADERBOARD.decode('utf8') % (rows)
+ return html
+
+ def get_flag(self, team):
+ flag = self.data.get_team_image(team)
+ return '' if flag is None else p_temp.LEADERBOARD_ROW_FLAG % (flag)
diff --git a/jfr_playoff/settings.py b/jfr_playoff/settings.py
new file mode 100644
index 0000000..da92458
--- /dev/null
+++ b/jfr_playoff/settings.py
@@ -0,0 +1,41 @@
+import glob
+import json
+import readline
+import sys
+
+
+def complete_filename(text, state):
+ return (glob.glob(text+'*')+[None])[state]
+
+
+class PlayoffSettings(object):
+
+ def __init__(self):
+ self.settings = None
+ self.interactive = False
+ self.settings_file = None
+ if len(sys.argv) > 1:
+ self.settings_file = sys.argv[1]
+ else:
+ self.interactive = True
+
+ def load(self):
+ if self.settings_file is None:
+ readline.set_completer_delims(' \t\n;')
+ readline.parse_and_bind("tab: complete")
+ readline.set_completer(complete_filename)
+ self.settings_file = raw_input('JSON settings file: ')
+
+ if self.settings is None:
+ self.settings = json.load(open(self.settings_file))
+
+ def has_section(self, key):
+ self.load()
+ return key in self.settings
+
+ def get(self, *keys):
+ self.load()
+ section = self.settings
+ for key in keys:
+ section = section[key]
+ return section
diff --git a/jfr_playoff/sql.py b/jfr_playoff/sql.py
new file mode 100644
index 0000000..b01bd08
--- /dev/null
+++ b/jfr_playoff/sql.py
@@ -0,0 +1,36 @@
+MATCH_RESULTS = '''
+SELECT t1.fullname, t2.fullname, matches.carry,
+ matches.vph, matches.vpv, matches.corrh, matches.corrv
+FROM #db#.matches matches
+JOIN #db#.teams t1
+ ON t1.id = #db#.matches.homet
+JOIN #db#.teams t2
+ ON t2.id = #db#.matches.visit
+WHERE matches.tabl = %s AND matches.rnd = %s
+'''
+
+BOARD_COUNT = '''
+SELECT segmentsperround*boardspersegment,
+ SUM(sc1.contract IS NOT NULL AND sc2.contract IS NOT NULL)
+FROM #db#.scores sc1
+JOIN #db#.scores sc2
+ ON sc1.rnd = sc2.rnd
+ AND sc1.segment = sc2.segment
+ AND sc1.tabl = sc2.tabl
+ AND sc1.board = sc2.board
+ AND sc1.room = 1
+ AND sc2.room = 2
+JOIN #db#.admin
+WHERE sc1.tabl = %s AND sc1.rnd = %s
+'''
+
+TOWEL_COUNT = '''
+SELECT #db#.admin.boardspersegment * SUM(#db#.segments.towel > 0)
+FROM #db#.segments
+JOIN #db#.admin
+WHERE #db#.segments.tabl = %s AND #db#.segments.rnd = %s
+'''
+
+PREFIX = '''
+SELECT shortname FROM #db#.admin
+'''
diff --git a/jfr_playoff/template.py b/jfr_playoff/template.py
new file mode 100644
index 0000000..99e6225
--- /dev/null
+++ b/jfr_playoff/template.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+MATCH_TABLE = '''
+<table border="0" cellspacing="0">
+<tr>
+<td class="s12" width="%d">&nbsp;</td>
+<td class="bdcc2" width="%d">&nbsp;wynik&nbsp;</td>
+</tr>
+%s
+</table>
+'''
+
+MATCH_TEAM_ROW = '''
+<tr class="%s">
+<td class="bd1">&nbsp;<a href="%s" onmouseover="Tip('%s')" onmouseout="UnTip()">%s</a>&nbsp;</td>
+<td class="bdc">
+<a href="%s" target="_top">
+&nbsp;%.1f&nbsp;
+</a>
+</td>
+</tr>
+'''
+
+MATCH_RUNNING = '''
+<a href="%s" target="_top">
+<img src="images/A.gif" />
+%d
+<img src="images/A.gif" />
+</a>
+'''
+
+MATCH_GRID = '''
+<div style="position: relative; width: %dpx; height: %dpx; margin: 10px">
+<canvas width="%d" height="%d" id="playoff_canvas" %s></canvas>
+%s
+<script src="sklady/playoff.js" type="text/javascript"></script>
+</div>
+'''
+
+MATCH_GRID_PHASE = '''
+<a href="%s" target="_top" style="display: inline-block; width: %dpx; text-align: center; position: absolute; top: 0; left: %dpx">
+<font size="4">%s</font>
+</a>
+'''
+
+MATCH_GRID_PHASE_RUNNING = '''
+<a href="%s" target="_top" style="display: inline-block; width: %dpx; text-align: center; position: absolute; top: 0; left: %dpx">
+<img src="images/A.gif" />
+<font size="4">%s</font>
+<img src="images/A.gif" />
+</a>
+'''
+
+MATCH_BOX = '''
+<div style="text-align: center; position: absolute; left: %dpx; top: %dpx" data-id="%d" data-winner="%s" data-loser="%s" class="playoff_matchbox">
+%s
+</div>
+'''
+
+LEADERBOARD = '''
+<table border="0" cellspacing="0">
+<tr>
+<td class="bdnl12" colspan="2" align="center"><b>&nbsp;KLASYFIKACJA KOŃCOWA&nbsp;</b></td>
+</tr>
+<tr>
+<td class="e" colspan="2">&nbsp;</td>
+</tr>
+<tr>
+<td class="bdcc12">&nbsp;miejsce&nbsp;</td>
+<td class="bdcc2">&nbsp;drużyna&nbsp;</td>
+</tr>
+%s
+</table>
+'''
+
+LEADERBOARD_ROW = '''
+<tr>
+<td class="bdc1">%d</td>
+<td class="bd">
+&nbsp;%s&nbsp;&nbsp;%s&nbsp;
+</td>
+</tr>
+'''
+
+LEADERBOARD_ROW_FLAG = '''
+<img class="fl" src="images/%s" />
+'''
+
+PAGE_HEAD = '''
+<meta http-equiv="Pragma" content="no-cache" />
+<meta http-equiv="Cache-Control" content="no-cache" />
+<meta name="robots" content="noarchive" />
+<meta http-equiv="expires" content="0" />
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta name="Generator" content="PlayOff" />
+%s
+<title>%s</title>
+<link rel="stylesheet" type="text/css" href="css/kolorki.css" />
+<script type="text/javascript" src="sklady/myAjax.js"></script>
+'''
+
+PAGE_HEAD_REFRESH = '''
+<meta http-equiv="Refresh" content="%d" />
+'''
+
+PAGE_BODY = '''
+<script type="text/javascript" src="sklady/wz_tooltip.js"></script>
+%s
+%s
+%s
+%s
+'''
+
+PAGE_BODY_FOOTER = '''
+<p class="f">&nbsp;Admin&nbsp;&copy;Jan Romański&#39;2005, PlayOff&nbsp;&copyMichał Klichowicz&#39;2017, strona wygenerowana %s</p>
+'''
+
+PAGE = '''
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+%s
+</head>
+<body class="all">
+%s
+</body>
+</html>
+'''