diff options
-rw-r--r-- | src/bidding_data.py | 43 | ||||
-rw-r--r-- | src/bidding_data_gui.py | 221 |
2 files changed, 153 insertions, 111 deletions
diff --git a/src/bidding_data.py b/src/bidding_data.py index 3b277bb..739df96 100644 --- a/src/bidding_data.py +++ b/src/bidding_data.py @@ -5,14 +5,15 @@ Utility to insert HTML tables with bidding data into traveller files generated by JFR Pary. """ -import sys import glob +import logging as log import re import socket +import sys +from os import path, remove + import pypyodbc -import logging as log -from os import path, remove from bs4 import BeautifulSoup as bs4 __version__ = '1.0.3' @@ -356,6 +357,9 @@ class JFRBidding(object): # all generated bidding table files, for cleanup purposes __bidding_files = [] + # configuration for Goniec + __goniec = {'host': None, 'port': None} + def __init__(self, bws_file, file_prefix, goniec_setup=None): """Construct parser object.""" log.getLogger('init').debug('reading BWS file: %s', bws_file) @@ -382,13 +386,12 @@ class JFRBidding(object): log.getLogger('init').debug('tournament files pattern: %s', self.__tournament_files_match.pattern) self.__map_board_numbers() - self.__goniec_host = None if goniec_setup is not None: setup_parts = goniec_setup.split(':') - self.__goniec_host = setup_parts[0] \ - if len(setup_parts) > 0 else 'localhost' - self.__goniec_port = int(setup_parts[1]) \ - if len(setup_parts) > 1 else 8090 + self.__goniec['host'] = setup_parts[0] if len(setup_parts) > 0 \ + else 'localhost' + self.__goniec['port'] = int(setup_parts[1]) \ + if len(setup_parts) > 1 else 8090 def write_bidding_tables(self): """Iterate over bidding and writes tables to HTML files.""" @@ -505,7 +508,8 @@ class JFRBidding(object): return used_board_files def send_changed_files(self, files_to_send): - if self.__goniec_host is not None: + """Send specified files from working directory via Goniec.""" + if self.__goniec['host'] is not None: working_directory = path.dirname(self.__tournament_prefix) \ + path.sep files_to_send = [file_to_send.replace(working_directory, '', 1) @@ -513,23 +517,26 @@ class JFRBidding(object): if file_to_send.startswith(working_directory)] try: goniec_socket = socket.socket() - goniec_socket.connect((self.__goniec_host, self.__goniec_port)) + goniec_socket.connect(( + self.__goniec['host'], + self.__goniec['port'])) log.getLogger('goniec').info( - 'connected to Goniec at %s:%d' \ - % (self.__goniec_host, self.__goniec_port)) - content_lines = [working_directory] + files_to_send + ['bye', ''] + 'connected to Goniec at %s:%d', + self.__goniec['host'], self.__goniec['port']) + content_lines = [working_directory] + \ + files_to_send + ['bye', ''] goniec_socket.sendall('\n'.join( [line.encode(sys.getfilesystemencoding()) for line in content_lines])) log.getLogger('goniec').info( - 'working directory is: %s' % working_directory) + 'working directory is: %s', working_directory) goniec_socket.close() for file_sent in files_to_send: log.getLogger('goniec').info( - 'sent file to Goniec: %s' % file_sent) - except socket.error as er: + 'sent file to Goniec: %s', file_sent) + except socket.error as err: log.getLogger('goniec').error( - 'unable to connect to Goniec: %s' % er) + 'unable to connect to Goniec: %s', err) def main(): @@ -611,7 +618,7 @@ def main(): all_files += bidding_parser.write_bidding_links() bidding_parser.send_changed_files(all_files) except Exception as ex: - #log.getLogger('root').error(ex.strerror) + log.getLogger('root').error(ex) raise log.info('--------- program ended ---------') diff --git a/src/bidding_data_gui.py b/src/bidding_data_gui.py index 5ad2647..3a49b83 100644 --- a/src/bidding_data_gui.py +++ b/src/bidding_data_gui.py @@ -7,27 +7,31 @@ Graphical user interface to insert HTML tables with bidding data into traveller files generated by JFR Pary. """ -import Tkinter as tk -import tkFileDialog -import tkMessageBox -import Queue - +import json import logging as log import os +import Queue import socket -import json import threading +import tkFileDialog +import tkMessageBox + +import Tkinter as tk from bidding_data import __version__ as bidding_data_version +# config file path +CONFIG_FILE = 'config.json' + + class BiddingGUI(tk.Frame): """GUI frame class.""" - # Tk variable to store tournament result file path - __tour_filename = None - # Tk variable to store BWS file path - __bws_filename = None + # Tk variables to bind widget values + __variables = {} + # widgets which are toggled in Goniec settings panel + __goniec_widgets = [] def run_bidding_data(self): """ @@ -43,28 +47,28 @@ class BiddingGUI(tk.Frame): self.__gui_logger.reset_counts() # check for input parameter paths - if not os.path.exists(self.__bws_filename.get()): + if not os.path.exists(self.__variables['bws_filename'].get()): raise Exception('BWS file not found') - if not os.path.exists(self.__tour_filename.get()): + if not os.path.exists(self.__variables['tour_filename'].get()): raise Exception('Tournament results file not found') # Goniec parameters/switches - goniec_params = '%s:%d' % (self.__goniec_host.get(), - self.__goniec_port.get()) \ - if self.__goniec_enabled.get() == 1 \ - else None + goniec_params = '%s:%d' % ( + self.__variables['goniec_host'].get(), + self.__variables['goniec_port'].get() + ) if self.__variables['goniec_enabled'].get() == 1 else None # do the magic from bidding_data import JFRBidding parser = JFRBidding( - bws_file=self.__bws_filename.get(), - file_prefix=self.__tour_filename.get(), + bws_file=self.__variables['bws_filename'].get(), + file_prefix=self.__variables['tour_filename'].get(), goniec_setup=goniec_params) changed_files = [] changed_files += parser.write_bidding_tables() changed_files += parser.write_bidding_scripts() changed_files += parser.write_bidding_links() - if self.__goniec_enabled.get() == 1: + if self.__variables['goniec_enabled'].get() == 1: parser.send_changed_files(changed_files) # inform of any warnings/errors that might have occuerd @@ -103,7 +107,7 @@ class BiddingGUI(tk.Frame): Displays file selection dialog for tournament file and stores user's choice in Tk variable. """ - self.__tour_filename.set(tkFileDialog.askopenfilename( + self.__variables['tour_filename'].set(tkFileDialog.askopenfilename( title='Wybierz główny plik wyników turnieju', filetypes=[('HTML files', '.htm*'), ('all files', '.*')])) @@ -115,7 +119,7 @@ class BiddingGUI(tk.Frame): Displays file selection dialog for tournament file and stores user's choice in Tk variable. """ - self.__bws_filename.set(tkFileDialog.askopenfilename( + self.__variables['bws_filename'].set(tkFileDialog.askopenfilename( title='Wybierz plik z danymi licytacji', filetypes=[('BWS files', '.bws'), ('all files', '.*')])) @@ -132,38 +136,39 @@ class BiddingGUI(tk.Frame): def toggle_goniec(self): """Toggle state for Goniec-related controls on Goniec switch toggle.""" - for control in [ - self.goniec_host_label, self.goniec_host_field, - self.goniec_port_label, self.goniec_port_field, - self.goniec_test_btn]: - self.queue(control.__setitem__, 'state', tk.NORMAL - if self.__goniec_enabled.get() == 1 else tk.DISABLED) + for control in self.__goniec_widgets: + self.queue( + control.__setitem__, 'state', + tk.NORMAL if self.__variables['goniec_enabled'].get() == 1 + else tk.DISABLED + ) def test_goniec(self): """Test connectivity with Goniec and display a message accordingly.""" goniec_socket = socket.socket() try: - goniec_socket.connect((self.__goniec_host.get(), - self.__goniec_port.get())) + goniec_socket.connect((self.__variables['goniec_host'].get(), + self.__variables['goniec_port'].get())) goniec_socket.close() self.queue( tkMessageBox.showinfo, 'Hurra!', 'Goniec - albo coś, co go udaje - działa!') - except socket.error as err: + except socket.error: self.queue( tkMessageBox.showerror, 'Buuu...', 'Pod podanym adresem Goniec nie działa :(') except (ValueError, OverflowError): self.queue( tkMessageBox.showerror, 'Buuu...', - 'Parametry Gońca mają niewłaściwy format, czemu mi to robisz :(') + 'Parametry Gońca mają niewłaściwy format, ' + + 'czemu mi to robisz :(') def on_close(self): - """Handles root window WM_DELETE_WINDOW message.""" + """Handle root window WM_DELETE_WINDOW message.""" try: self.__store_config() - except: - log.getLogger('config').error('Could not save config file') + except (ValueError, TypeError, OverflowError) as ex: + log.getLogger('config').error('Could not save config file: %s', ex) self.master.destroy() # GUI message queue (for background thread interaction) @@ -194,17 +199,22 @@ class BiddingGUI(tk.Frame): Initializes window appearence, controls, layout and logging facility. """ tk.Frame.__init__(self, master) - # bind Tk variables to input parameter paths - self.__tour_filename = tk.StringVar(master=self) - self.__bws_filename = tk.StringVar(master=self) - # and to Goniec parameters - self.__goniec_host = tk.StringVar(master=self) - self.__goniec_port = tk.IntVar(master=self) - # "boolean" variable to hold chackbox state - self.__goniec_enabled = tk.IntVar(master=self) + + self.__variables = { + # bind Tk variables to input parameter paths + 'tour_filename': tk.StringVar(master=self), + 'bws_filename': tk.StringVar(master=self), + # and to Goniec parameters + 'goniec_host': tk.StringVar(master=self), + 'goniec_port': tk.IntVar(master=self), + # "boolean" variable to hold chackbox state + 'goniec_enabled': tk.IntVar(master=self) + } + # set window title and icon self.master.title('JBBD - JFR/BWS bidding data') self.__set_icon(self.__icon_data) + # create controls self.__create_widgets() # and align them within a layout @@ -212,8 +222,10 @@ class BiddingGUI(tk.Frame): self.__configure_grid_cells([1], [5]) # main frame should fill entire application window self.pack(expand=1, fill=tk.BOTH) + # finally, set logging up self.__configure_logging() + # default config values self.__default_config = { 'paths': { @@ -226,12 +238,13 @@ class BiddingGUI(tk.Frame): 'port': 8090 } } - # config file path - self.__config_file = 'config.json' + # restore config from file self.__restore_config() + # register on-close hook self.master.protocol("WM_DELETE_WINDOW", self.on_close) + # fire up interthread queue self.after(100, self.process_queue) @@ -266,7 +279,8 @@ class BiddingGUI(tk.Frame): self, text='Plik turnieju:') # text field for tournament file path tour_entry = tk.Entry( - self, state=tk.DISABLED, textvariable=self.__tour_filename) + self, state=tk.DISABLED, + textvariable=self.__variables['tour_filename']) # tournament selection button tour_select_btn = tk.Button( self, text='Szukaj', command=self.tour_select) @@ -280,7 +294,8 @@ class BiddingGUI(tk.Frame): self, text='BWS:') # text field for BWS file path bws_entry = tk.Entry( - self, state=tk.DISABLED, textvariable=self.__bws_filename) + self, state=tk.DISABLED, + textvariable=self.__variables['bws_filename']) # BWS selection button bws_select_btn = tk.Button( self, text='Szukaj', command=self.bws_select) @@ -307,63 +322,72 @@ class BiddingGUI(tk.Frame): # fourth row, rightmost 1/3 of window width quit_btn.grid(row=3, column=4, columnspan=3, sticky=tk.E+tk.W) + self.__create_goniec_widgets() + + # vertical scrollbar for log output field + log_scroll_y = tk.Scrollbar(self, orient=tk.VERTICAL) + # horizontal scrollbar for log output field + log_scroll_x = tk.Scrollbar(self, orient=tk.HORIZONTAL) + # log field, bound (both ways) to scrollbars + self.log_field = tk.Text( + self, height=5, width=80, wrap=tk.NONE, + xscrollcommand=log_scroll_x.set, + yscrollcommand=log_scroll_y.set) + log_scroll_x['command'] = self.log_field.xview + log_scroll_y['command'] = self.log_field.yview + # fifth row, entries window width, expands with window + self.log_field.grid( + row=5, column=0, columnspan=6, sticky=tk.N+tk.S+tk.E+tk.W) + # scrollbars to the right and to the bottom of the field + log_scroll_y.grid(row=5, column=6, sticky=tk.N+tk.S) + log_scroll_x.grid(row=6, column=0, columnspan=6, sticky=tk.E+tk.W) + + def __create_goniec_widgets(self): # Goniec toggle checkbox - self.goniec_checkbox = tk.Checkbutton( + goniec_checkbox = tk.Checkbutton( self, text='Ślij Gońcem', command=self.toggle_goniec, - variable=self.__goniec_enabled) + variable=self.__variables['goniec_enabled']) # fifth row, leftmost column - self.goniec_checkbox.grid( + goniec_checkbox.grid( row=4, column=0) # label for Goniec host entry field - self.goniec_host_label = tk.Label( + goniec_host_label = tk.Label( self, text='Host:') # fifth row, second column, aligned to the right - self.goniec_host_label.grid( + goniec_host_label.grid( row=4, column=1, sticky=tk.E) # Goniec host entry field - self.goniec_host_field = tk.Entry( - self, textvariable=self.__goniec_host) + goniec_host_field = tk.Entry( + self, textvariable=self.__variables['goniec_host']) # fifth row, third column, aligned to the left - self.goniec_host_field.grid( + goniec_host_field.grid( row=4, column=2, sticky=tk.W) # label for Goniec port entry field - self.goniec_port_label = tk.Label( + goniec_port_label = tk.Label( self, text='Port:') # fifth row, fourth column, aligned to the right - self.goniec_port_label.grid( + goniec_port_label.grid( row=4, column=3, sticky=tk.E) # Goniec port entry field - self.goniec_port_field = tk.Entry( - self, textvariable=self.__goniec_port) + goniec_port_field = tk.Entry( + self, textvariable=self.__variables['goniec_port']) # fifth row, fifth column, aligned to the left - self.goniec_port_field.grid( + goniec_port_field.grid( row=4, column=4, sticky=tk.W) # Goniec test button - self.goniec_test_btn = tk.Button( + goniec_test_btn = tk.Button( self, text='Test Gońca', command=self.test_goniec) # fifth row, rightmost column - self.goniec_test_btn.grid( + goniec_test_btn.grid( row=4, column=5) - # vertical scrollbar for log output field - log_scroll_y = tk.Scrollbar(self, orient=tk.VERTICAL) - # horizontal scrollbar for log output field - log_scroll_x = tk.Scrollbar(self, orient=tk.HORIZONTAL) - # log field, bound (both ways) to scrollbars - self.log_field = tk.Text( - self, height=5, width=80, wrap=tk.NONE, - xscrollcommand=log_scroll_x.set, - yscrollcommand=log_scroll_y.set) - log_scroll_x['command'] = self.log_field.xview - log_scroll_y['command'] = self.log_field.yview - # fifth row, entries window width, expands with window - self.log_field.grid( - row=5, column=0, columnspan=6, sticky=tk.N+tk.S+tk.E+tk.W) - # scrollbars to the right and to the bottom of the field - log_scroll_y.grid(row=5, column=6, sticky=tk.N+tk.S) - log_scroll_x.grid(row=6, column=0, columnspan=6, sticky=tk.E+tk.W) + # aggregate all widgets for which goniec_checkbox toggles status + self.__goniec_widgets = [ + goniec_host_label, goniec_host_field, + goniec_port_label, goniec_port_field, + goniec_test_btn] def __configure_logging(self): """Set up logging facility, bound to log output field.""" @@ -426,30 +450,41 @@ class BiddingGUI(tk.Frame): def __restore_config(self): """Read config from JSON file.""" try: - if os.path.exists(self.__config_file): - self.__default_config = json.load(file(self.__config_file)) + if os.path.exists(CONFIG_FILE): + self.__default_config = json.load(file(CONFIG_FILE)) else: log.getLogger('config').info( 'Config does not exist, using defaults') - except: + except ValueError as ex: log.getLogger('config').warning( - 'Could not load complete config from file') + 'Could not load complete config from file: %s', ex) finally: - self.__tour_filename.set(self.__default_config['paths']['html']) - self.__bws_filename.set(self.__default_config['paths']['bws']) - self.__goniec_host.set(self.__default_config['goniec']['host']) - self.__goniec_port.set(self.__default_config['goniec']['port']) - self.__goniec_enabled.set(self.__default_config['goniec']['enabled']) + self.__variables['tour_filename'].set( + self.__default_config['paths']['html']) + self.__variables['bws_filename'].set( + self.__default_config['paths']['bws']) + self.__variables['goniec_host'].set( + self.__default_config['goniec']['host']) + self.__variables['goniec_port'].set( + self.__default_config['goniec']['port']) + self.__variables['goniec_enabled'].set( + self.__default_config['goniec']['enabled']) self.toggle_goniec() def __store_config(self): """Write config to JSON file.""" - self.__default_config['paths']['html'] = self.__tour_filename.get() - self.__default_config['paths']['bws'] = self.__bws_filename.get() - self.__default_config['goniec']['host'] = self.__goniec_host.get() - self.__default_config['goniec']['port'] = self.__goniec_port.get() - self.__default_config['goniec']['enabled'] = self.__goniec_enabled.get() - json.dump(self.__default_config, file(self.__config_file, 'w'), + self.__default_config = { + 'paths': { + 'html': self.__variables['tour_filename'].get(), + 'bws': self.__variables['bws_filename'].get() + }, + 'goniec': { + 'host': self.__variables['goniec_host'].get(), + 'port': self.__variables['goniec_port'].get(), + 'enabled': self.__variables['goniec_enabled'].get() + } + } + json.dump(self.__default_config, file(CONFIG_FILE, 'w'), sort_keys=True, indent=4) # embedded image data for app icon |