From 8b0422b1b0d3ed3221156bc953a838d6dcefed9e Mon Sep 17 00:00:00 2001 From: luxick Date: Mon, 5 Mar 2018 22:57:14 +0100 Subject: [PATCH] Additional loading functions. --- dsst/dsst_gtk3/client.py | 32 +++++++-------- dsst/dsst_gtk3/gtk_ui.py | 54 ++++++++++++++++--------- dsst/dsst_gtk3/handlers/handlers.py | 1 - dsst/dsst_gtk3/reload.py | 15 ++++--- dsst/dsst_gtk3/util.py | 22 +++++++--- dsst/dsst_server/data_access/mapping.py | 32 +++++++++++++-- dsst/dsst_server/read_functions.py | 9 +++++ dsst/dsst_server/server.py | 36 +++++++++++------ dsst/dsst_server/tokens.py | 5 +++ 9 files changed, 140 insertions(+), 66 deletions(-) create mode 100644 dsst/dsst_server/tokens.py diff --git a/dsst/dsst_gtk3/client.py b/dsst/dsst_gtk3/client.py index d024914..b7da4b3 100644 --- a/dsst/dsst_gtk3/client.py +++ b/dsst/dsst_gtk3/client.py @@ -8,31 +8,31 @@ except ImportError: class Access: - def __init__(self, connection): - self.host = connection.get('host') - self.port = connection.get('port') - self.buffer = connection.get('buffer_size') - self.auth_key = connection.get('auth_key') - self.socket = socket.socket() + def __init__(self, conn_dict): + self.host = conn_dict.get('host') + self.port = conn_dict.get('port') + self.buffer = conn_dict.get('buffer_size') + self.auth_token = conn_dict.get('auth_token') def send_request(self, action: str, *args): - request = {'auth_key': self.auth_key, + request = {'auth_token': self.auth_token, 'action': action, 'args': args} request = pickle.dumps(request) + soc = socket.socket() try: - self.socket.connect((self.host, self.port)) - util.send_msg(self.socket, request) - response = util.recv_msg(self.socket) - response = pickle.loads(response) - if not response.get('success'): - raise Exception(response.get('message')) + soc.connect((self.host, self.port)) + util.send_msg(soc, request) + message = util.recv_msg(soc) + message = pickle.loads(message) + if not message.get('success'): + raise Exception(message.get('message')) finally: - self.socket.close() - return response.get('data') + soc.close() + return message.get('data') if __name__ == '__main__': - access = Access({'host': 'europa', 'port': 12345, 'buffer_size': 1024, 'auth_key': 'a'}) + access = Access({'host': 'europa', 'port': 12345, 'buffer_size': 1024, 'auth_token': 'a'}) action = 'load_seasons' response = access.send_request(action) pp = pprint.PrettyPrinter(indent=1) diff --git a/dsst/dsst_gtk3/gtk_ui.py b/dsst/dsst_gtk3/gtk_ui.py index 330a325..1a2c364 100644 --- a/dsst/dsst_gtk3/gtk_ui.py +++ b/dsst/dsst_gtk3/gtk_ui.py @@ -5,7 +5,7 @@ import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from dsst_gtk3.handlers import handlers -from dsst_gtk3 import util, reload +from dsst_gtk3 import util, reload, client import sql_func import sql @@ -26,27 +26,41 @@ class GtkUi: self.ui.connect_signals(self.handlers) # Show all widgets self.ui.get_object('main_window').show_all() - db_config = config['sql_connections'][0] - # Initialize the database - sql.db.init(db_config['db_name'], host=db_config['host'], port=db_config['port'], - user=db_config['user'], password=db_config['password']) - # Show database info in status bar - self.set_db_status_label(db_config) - # Create database if not exists - sql_func.create_tables() - self.reload() + # Connect to data server + config = config['servers'][0] + self.data_client = client.Access(config) + self.data = {} + # Load base data and seasons + self.initial_load() + + def initial_load(self): + with util.handle_exception(Exception): + self.data['players'] = self.data_client.send_request('load_players') + self.data['drinks'] = self.data_client.send_request('load_drinks') + self.data['seasons'] = self.data_client.send_request('load_seasons') + reload.reload_base_data(self.ui, self) def reload(self): - reload.reload_base_data(self.ui, self) - season_id = self.get_selected_season_id() - if season_id: - reload.reload_episodes(self.ui, self, season_id) - reload.reload_season_stats(self.ui, self, season_id) - else: - return - episode_id = self.get_selected_episode_id() - if episode_id: - reload.reload_episode_stats(self.ui, self, episode_id) + with util.handle_exception(Exception): + self.data['episodes'] = self.data_client.send_request('load_episodes', self.get_selected_season_id()) + reload.reload_episodes(self.ui, self) + pass + # reload.reload_base_data(self.ui, self) + # season_id = self.get_selected_season_id() + # if season_id: + # reload.reload_episodes(self.ui, self, season_id) + # reload.reload_season_stats(self.ui, self, season_id) + # else: + # return + # episode_id = self.get_selected_episode_id() + # if episode_id: + # reload.reload_episode_stats(self.ui, self, episode_id) + + def load(self, data_dict: dict, value_field: str, request_name: str): + try: + data_dict[value_field] = self.data_client.send_request('request_name') + except Exception as e: + print() def set_db_status_label(self, db_conf: dict): self.ui.get_object('connection_label').set_text(f'{db_conf["user"]}@{db_conf["host"]}') diff --git a/dsst/dsst_gtk3/handlers/handlers.py b/dsst/dsst_gtk3/handlers/handlers.py index 182e1c2..748d5ae 100644 --- a/dsst/dsst_gtk3/handlers/handlers.py +++ b/dsst/dsst_gtk3/handlers/handlers.py @@ -29,7 +29,6 @@ class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers, """ Signal will be sent when app should close :param _: Arguments to the delete event """ - sql.db.close() Gtk.main_quit() # DEBUG Functions ################################################################################################## diff --git a/dsst/dsst_gtk3/reload.py b/dsst/dsst_gtk3/reload.py index adff4ee..d352e3b 100644 --- a/dsst/dsst_gtk3/reload.py +++ b/dsst/dsst_gtk3/reload.py @@ -3,21 +3,21 @@ from collections import Counter from gi.repository import Gtk from data_access import sql, sql_func -from dsst_gtk3 import util +from dsst_gtk3 import util, gtk_ui -def reload_base_data(builder: Gtk.Builder, app: 'gtk_ui.GtkUi'): +def reload_base_data(builder: Gtk.Builder, app: 'gtk_ui.GtkUi',): """Reload function for all base data witch is not dependant on a selected season or episode :param app: GtkUi instance :param builder: Gtk.Builder with loaded UI """ # Rebuild all players store builder.get_object('all_players_store').clear() - for player in sql.Player.select(): + for player in app.data['players']: builder.get_object('all_players_store').append([player.id, player.name, player.hex_id]) # Rebuild drink store builder.get_object('drink_store').clear() - for drink in sql.Drink.select(): + for drink in app.data['drinks']: builder.get_object('drink_store').append([drink.id, drink.name, '{:.2f}%'.format(drink.vol)]) # Rebuild seasons store combo = builder.get_object('season_combo_box') # type: Gtk.ComboBox @@ -25,23 +25,22 @@ def reload_base_data(builder: Gtk.Builder, app: 'gtk_ui.GtkUi'): with util.block_handler(combo, app.handlers.do_season_selected): store = builder.get_object('seasons_store') store.clear() - for season in sql.Season.select().order_by(sql.Season.number): + for season in app.data['seasons']: store.append([season.id, season.game_name]) combo.set_active(active) -def reload_episodes(builder: Gtk.Builder, app: 'gtk_ui.GtkUi', season_id: int): +def reload_episodes(builder: Gtk.Builder, app: 'gtk_ui.GtkUi'): """Reload all data that is dependant on a selected season :param app: GtkUi instance :param builder: Gtk.Builder with loaded UI - :param season_id: ID of the season for witch to load data """ # Rebuild episodes store selection = builder.get_object('episodes_tree_view').get_selection() with util.block_handler(selection, app.handlers.on_selected_episode_changed): model, selected_paths = selection.get_selected_rows() model.clear() - for episode in sql_func.get_episodes_for_season(season_id): + for episode in app.data['episodes']: model.append([episode.id, episode.name, str(episode.date), episode.number]) if selected_paths: selection.select_path(selected_paths[0]) diff --git a/dsst/dsst_gtk3/util.py b/dsst/dsst_gtk3/util.py index 391525f..39d921e 100644 --- a/dsst/dsst_gtk3/util.py +++ b/dsst/dsst_gtk3/util.py @@ -5,18 +5,17 @@ import json import os from contextlib import contextmanager from gi.repository import Gtk -from typing import Callable +from typing import Callable, Type from zipfile import ZipFile CONFIG_PATH = os.path.join(os.path.expanduser('~'), '.config', 'dsst', 'config.json') DEFAULT_CONFIG = { 'auto_connect': False, - 'sql_connections': [{ + 'servers': [{ 'host': 'localhost', - 'port': 3306, - 'db_name': 'dsst', - 'user': 'dsst', - 'password': 'dsst'} + 'port': 12345, + 'buffer_size': 1024, + 'auth_token': 'a'} ] } @@ -32,6 +31,17 @@ def block_handler(widget: 'Gtk.Widget', handler_func: Callable): widget.handler_unblock_by_func(handler_func) +@contextmanager +def handle_exception(exception: 'Type[Exception]'): + """Run operation in try/except block and display exception in a dialog + :param exception: + """ + try: + yield + except exception as e: + print(e) + + def get_combo_value(combo, index: int): """ Retrieve the selected value of a combo box at the selected index in the model :param combo: Any Gtk Widget that supports 'get_active_iter()' diff --git a/dsst/dsst_server/data_access/mapping.py b/dsst/dsst_server/data_access/mapping.py index 0c724ad..e8052a7 100644 --- a/dsst/dsst_server/data_access/mapping.py +++ b/dsst/dsst_server/data_access/mapping.py @@ -10,20 +10,46 @@ def map_base_fields(cls, db_model): return model +def db_to_drink(drink: 'sql.Drink'): + return map_base_fields(models.Drink, drink) + + def db_to_enemy(enemy: 'sql.Enemy'): return map_base_fields(models.Enemy, enemy) def db_to_player(player: 'sql.Player'): - model = map_base_fields(models.Player, player) + return map_base_fields(models.Player, player) + + +def db_to_penalty(penalty: 'sql.Penalty'): + model = map_base_fields(models.Penalty, penalty) + model.drink = db_to_drink(penalty.drink) + model.player = db_to_player(penalty.player) + return model + + +def db_to_death(death: 'sql.Death'): + model = map_base_fields(models.Death, death) + model.player = db_to_player(death.player) + model.enemy = db_to_enemy(death.enemy) + model.penalties = [db_to_penalty(penalty) for penalty in death.penalties] + return model + + +def db_to_victory(victory: 'sql.Victory'): + model = map_base_fields(models.Victory, victory) + model.player = db_to_player(victory.player) + model.enemy = db_to_enemy(victory.enemy) return model def db_to_episode(episode: 'sql.Episode'): model = map_base_fields(models.Episode, episode) model.players = [db_to_player(player) for player in episode.players] - model.deaths = [] - model.victories = [] + model.deaths = [db_to_death(death) for death in episode.deaths] + model.victories = [db_to_victory(victory) for victory in episode.victories] + return model def db_to_season(season: 'sql.Season'): diff --git a/dsst/dsst_server/read_functions.py b/dsst/dsst_server/read_functions.py index e7c5187..7e79272 100644 --- a/dsst/dsst_server/read_functions.py +++ b/dsst/dsst_server/read_functions.py @@ -13,7 +13,16 @@ class ReadFunctions: def load_seasons_all(*_): return [shortcuts.model_to_dict(season, backrefs=True, max_depth=2) for season in sql.Season.select()] + @staticmethod + def load_episodes(season_id, *_): + if not season_id: + raise Exception('Exception: Missing argument (season_id)') + return [mapping.db_to_episode(ep) for ep in sql.Season.get(sql.Season.id == season_id).episodes] + @staticmethod def load_players(*_): return [mapping.db_to_player(player) for player in sql.Player.select()] + @staticmethod + def load_drinks(*_): + return [mapping.db_to_drink(drink) for drink in sql.Drink.select()] \ No newline at end of file diff --git a/dsst/dsst_server/server.py b/dsst/dsst_server/server.py index 1eb5a3d..d48aedb 100644 --- a/dsst/dsst_server/server.py +++ b/dsst/dsst_server/server.py @@ -6,7 +6,7 @@ import sys import os from common import util, models -from dsst_server import read_functions, write_functions +from dsst_server import read_functions, write_functions, tokens from dsst_server.func_proxy import FunctionProxy from dsst_server.data_access import sql @@ -23,12 +23,19 @@ class DsstServer: self.socket_server.bind((HOST, PORT)) print(f'Bound socket to {PORT} on host {HOST}') - self.read_actions = util.list_class_methods(read_functions.ReadFunctions) - self.write_actions = util.list_class_methods(write_functions.WriteFunctions) + # Initialize database sql.db.init('dsst', user='dsst', password='dsst') + print(f'Database initialized ({sql.db.database})') - self.key_access = {'a': self.read_actions, - 'b': self.read_actions + self.write_actions} + # Load access tokens and map them to their allowed methods + read_actions = util.list_class_methods(read_functions.ReadFunctions) + write_actions = util.list_class_methods(write_functions.WriteFunctions) + parm_access = { + 'r': read_actions, + 'rw': read_actions + write_actions + } + self.tokens = {token: parm_access[perms] for token, perms in tokens.TOKENS} + print(f'Loaded auth tokens: {self.tokens.keys()}') def run(self): self.socket_server.listen(5) @@ -41,17 +48,22 @@ class DsstServer: data = util.recv_msg(client) request = pickle.loads(data) print(f'Request: {request}') - # Validate auth key in request - key = request.get('auth_key') - if key not in self.key_access: - util.send_msg(client, pickle.dumps({'success': False, 'message': 'Auth Key invalid'})) - print(f'Rejected request from {address}. Auth key invalid ({key})') + # Validate auth token in request + token = request.get('auth_token') + if token not in self.tokens: + util.send_msg(client, pickle.dumps({'success': False, 'message': 'Auth token invalid'})) + print(f'Rejected request from {address}. Auth token invalid ({token})') continue # Check read functions action_name = request.get('action') - if action_name in self.key_access[key]: + if action_name in self.tokens[token]: action = getattr(FunctionProxy, action_name) - value = action(request.get('args')) + try: + value = action(request.get('args')) + except Exception as e: + response = {'success': False, 'message': f'Exception was thrown on server.\n{e}'} + util.send_msg(client, pickle.dumps(response)) + raise response = {'success': True, 'data': value} util.send_msg(client, pickle.dumps(response)) continue diff --git a/dsst/dsst_server/tokens.py b/dsst/dsst_server/tokens.py new file mode 100644 index 0000000..57c1d90 --- /dev/null +++ b/dsst/dsst_server/tokens.py @@ -0,0 +1,5 @@ +# Define access tokens here +# i.E: { 'read': ['a', 'b''], +# 'write': ['a'] +# } +TOKENS = [('a', 'rw'), ('b', 'r')] \ No newline at end of file