diff options
Diffstat (limited to 'jfr_playoff')
-rw-r--r-- | jfr_playoff/__init__.py | 0 | ||||
-rw-r--r-- | jfr_playoff/data.py | 203 | ||||
-rw-r--r-- | jfr_playoff/db.py | 22 | ||||
-rw-r--r-- | jfr_playoff/dto.py | 23 | ||||
-rw-r--r-- | jfr_playoff/filemanager.py | 53 | ||||
-rw-r--r-- | jfr_playoff/generator.py | 126 | ||||
-rw-r--r-- | jfr_playoff/settings.py | 41 | ||||
-rw-r--r-- | jfr_playoff/sql.py | 36 | ||||
-rw-r--r-- | jfr_playoff/template.py | 128 |
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"> </td> +<td class="bdcc2" width="%d"> wynik </td> +</tr> +%s +</table> +''' + +MATCH_TEAM_ROW = ''' +<tr class="%s"> +<td class="bd1"> <a href="%s" onmouseover="Tip('%s')" onmouseout="UnTip()">%s</a> </td> +<td class="bdc"> +<a href="%s" target="_top"> + %.1f +</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> KLASYFIKACJA KOŃCOWA </b></td> +</tr> +<tr> +<td class="e" colspan="2"> </td> +</tr> +<tr> +<td class="bdcc12"> miejsce </td> +<td class="bdcc2"> drużyna </td> +</tr> +%s +</table> +''' + +LEADERBOARD_ROW = ''' +<tr> +<td class="bdc1">%d</td> +<td class="bd"> + %s %s +</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"> Admin ©Jan Romański'2005, PlayOff ©Michał Klichowicz'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> +''' |