From e5d0d92de24a90e16ca20605f227382d69fe1449 Mon Sep 17 00:00:00 2001 From: luxick Date: Wed, 7 Mar 2018 15:26:22 +0100 Subject: [PATCH] Update build script for client/server use. --- build.py | 61 ++++- dsst/common/models.py | 10 +- dsst/{ => dsst_gtk3}/__main__.py | 6 +- dsst/dsst_gtk3/dialogs.py | 248 +++++++++--------- dsst/dsst_gtk3/gtk_ui.py | 52 ++-- dsst/dsst_gtk3/handlers/base_data_handlers.py | 27 +- dsst/dsst_gtk3/handlers/death_handlers.py | 1 - dsst/dsst_gtk3/handlers/dialog_handlers.py | 11 +- dsst/dsst_gtk3/handlers/handlers.py | 5 +- dsst/dsst_gtk3/handlers/season_handlers.py | 6 +- dsst/dsst_gtk3/handlers/victory_handlers.py | 1 - dsst/dsst_gtk3/reload.py | 57 ++-- dsst/dsst_gtk3/resources/glade/window.glade | 29 +- dsst/dsst_gtk3/util.py | 12 +- dsst/dsst_server/__main__.py | 11 + dsst/dsst_server/data_access/mapping.py | 13 +- dsst/dsst_server/data_access/sql.py | 10 +- dsst/dsst_server/data_access/sql_func.py | 30 ++- dsst/dsst_server/read_functions.py | 21 +- dsst/dsst_server/server.py | 35 ++- 20 files changed, 382 insertions(+), 264 deletions(-) rename dsst/{ => dsst_gtk3}/__main__.py (57%) create mode 100644 dsst/dsst_server/__main__.py diff --git a/build.py b/build.py index a0ddd2f..3b2ad30 100644 --- a/build.py +++ b/build.py @@ -3,16 +3,59 @@ Package application using zipapp into an executable zip archive """ import os import zipapp +import sys + +import shutil INTERPRETER = '/usr/bin/env python3' -SOURCE_PATH = 'dsst' -TARGET_FILENAME = 'dsst' -# The bundled file should be placed into the build directory -target_path = os.path.join(os.path.dirname(__file__), 'build') +CLIENT_VERSION = '0.1' +SERVER_VERSION = '0.1' + +try: + build_mode = sys.argv[1] +except IndexError: + print('No build mode specified') + sys.exit(0) + +print('Building Mode: {}'.format(build_mode)) + +path = os.path.dirname(__file__) +# Specify build path +BUILD_PATH = os.path.join(path, 'build') # Make sure it exists -if not os.path.isdir(target_path): - os.mkdir(target_path) -target = os.path.join(target_path, TARGET_FILENAME) -# Create archive -zipapp.create_archive(source=SOURCE_PATH, target=target, interpreter=INTERPRETER) +if not os.path.isdir(BUILD_PATH): + os.mkdir(BUILD_PATH) + + +def build(target_filename, folder_name, entry_point): + source_path = os.path.join(BUILD_PATH, 'source') + if os.path.isdir(source_path): + shutil.rmtree(source_path) + os.mkdir(source_path) + shutil.copytree(os.path.join(path, 'dsst', folder_name), os.path.join(source_path, folder_name)) + shutil.copytree(os.path.join(path, 'dsst', 'common'), os.path.join(source_path, 'common')) + archive_name = os.path.join(BUILD_PATH, target_filename) + zipapp.create_archive(source=source_path, target=archive_name, interpreter=INTERPRETER, + main=entry_point) + print('Created {}'.format(archive_name)) + shutil.rmtree(source_path) + + +def build_server(): + build('dsst-server-{}'.format(SERVER_VERSION), 'dsst_server', 'dsst_server.server:main') + + +def build_gtk3(): + build('dsst-gtk3-{}'.format(CLIENT_VERSION), 'dsst_gtk3', 'dsst_gtk3.gtk_ui:main') + +build_modes = { + 'server': build_server, + 'gtk3': build_gtk3 +} + +if build_mode == 'all': + for mode, build_function in build_modes.items(): + build_function() +else: + build_modes[build_mode]() \ No newline at end of file diff --git a/dsst/common/models.py b/dsst/common/models.py index 96d2493..9e29bd7 100644 --- a/dsst/common/models.py +++ b/dsst/common/models.py @@ -45,7 +45,7 @@ class Enemy: def __init__(self, arg={}): self.id = arg.get('id') self.name = arg.get('name') - self.optional = arg.get('optional') + self.boss = arg.get('boss') self.season = arg.get('season') @@ -74,4 +74,10 @@ class Victory: self.info = arg.get('info') self.player = arg.get('player') self.enemy = arg.get('enemy') - self.episode = arg.get('episode') \ No newline at end of file + self.episode = arg.get('episode') + + +class SeasonStats: + def __init__(self, arg={}): + self.player_kd = arg.get('player_kd') + self.enemies = arg.get('enemies') diff --git a/dsst/__main__.py b/dsst/dsst_gtk3/__main__.py similarity index 57% rename from dsst/__main__.py rename to dsst/dsst_gtk3/__main__.py index 7c28945..dcb106a 100644 --- a/dsst/__main__.py +++ b/dsst/dsst_gtk3/__main__.py @@ -3,9 +3,9 @@ import sys # Add current directory to python path path = os.path.realpath(os.path.abspath(__file__)) -sys.path.insert(0, os.path.dirname(path)) +sys.path.insert(0, os.path.dirname(os.path.dirname(path))) -import gtk_ui +from dsst_gtk3 import gtk_ui if __name__ == '__main__': - gtk_ui.main() \ No newline at end of file + gtk_ui.main() diff --git a/dsst/dsst_gtk3/dialogs.py b/dsst/dsst_gtk3/dialogs.py index 614bb7c..ded4d30 100644 --- a/dsst/dsst_gtk3/dialogs.py +++ b/dsst/dsst_gtk3/dialogs.py @@ -1,12 +1,7 @@ """ This module contains UI functions for displaying different dialogs """ -import gi -gi.require_version('Gtk', '3.0') from gi.repository import Gtk -from datetime import datetime -import sql -from dsst_gtk3 import util def enter_string_dialog(builder: Gtk.Builder, title: str, value=None) -> str: @@ -33,7 +28,7 @@ def enter_string_dialog(builder: Gtk.Builder, title: str, value=None) -> str: return value -def show_episode_dialog(builder: Gtk.Builder, title: str, season_id: int, episode: sql.Episode=None): +def show_episode_dialog(builder: Gtk.Builder, title: str, season_id: int, episode): """ Shows a dialog to edit an episode :param builder: GtkBuilder with loaded 'dialogs.glade' :param title: Title of the dialog window @@ -41,44 +36,45 @@ def show_episode_dialog(builder: Gtk.Builder, title: str, season_id: int, episod :param episode: (Optional) Existing episode to edit :return True if changes where saved False if discarded """ - # Set up the dialog - dialog = builder.get_object("edit_episode_dialog") # type: Gtk.Dialog - dialog.set_transient_for(builder.get_object("main_window")) - dialog.set_title(title) - with sql.db.atomic(): - if not episode: - nxt_number = len(sql.Season.get_by_id(season_id).episodes) + 1 - episode = sql.Episode.create(seq_number=nxt_number, number=nxt_number, date=datetime.today(), - season=season_id) - # Set episode number - builder.get_object("episode_no_spin_button").set_value(episode.number) - # Set episode date - builder.get_object('episode_calendar').select_month(episode.date.month, episode.date.year) - builder.get_object('episode_calendar').select_day(episode.date.day) - # Set participants for the episode - builder.get_object('episode_players_store').clear() - for player in episode.players: - builder.get_object('episode_players_store').append([player.id, player.name, player.hex_id]) - - result = dialog.run() - dialog.hide() - - if result != Gtk.ResponseType.OK: - sql.db.rollback() - return False - - # Save all changes to Database - player_ids = [row[0] for row in builder.get_object('episode_players_store')] - # Insert new Players - episode.players = sql.Player.select().where(sql.Player.id << player_ids) - # Update Date of the Episode - cal_value = builder.get_object('episode_calendar').get_date() - selected_date = datetime(*cal_value).date() - episode.date = selected_date, - episode.number = int(builder.get_object("episode_no_spin_button").get_value()) - episode.name = builder.get_object("episode_name_entry").get_text() - episode.save() - return True + pass + # # Set up the dialog + # dialog = builder.get_object("edit_episode_dialog") # type: Gtk.Dialog + # dialog.set_transient_for(builder.get_object("main_window")) + # dialog.set_title(title) + # with sql.db.atomic(): + # if not episode: + # nxt_number = len(sql.Season.get_by_id(season_id).episodes) + 1 + # episode = sql.Episode.create(seq_number=nxt_number, number=nxt_number, date=datetime.today(), + # season=season_id) + # # Set episode number + # builder.get_object("episode_no_spin_button").set_value(episode.number) + # # Set episode date + # builder.get_object('episode_calendar').select_month(episode.date.month, episode.date.year) + # builder.get_object('episode_calendar').select_day(episode.date.day) + # # Set participants for the episode + # builder.get_object('episode_players_store').clear() + # for player in episode.players: + # builder.get_object('episode_players_store').append([player.id, player.name, player.hex_id]) + # + # result = dialog.run() + # dialog.hide() + # + # if result != Gtk.ResponseType.OK: + # sql.db.rollback() + # return False + # + # # Save all changes to Database + # player_ids = [row[0] for row in builder.get_object('episode_players_store')] + # # Insert new Players + # episode.players = sql.Player.select().where(sql.Player.id << player_ids) + # # Update Date of the Episode + # cal_value = builder.get_object('episode_calendar').get_date() + # selected_date = datetime(*cal_value).date() + # episode.date = selected_date, + # episode.number = int(builder.get_object("episode_no_spin_button").get_value()) + # episode.name = builder.get_object("episode_name_entry").get_text() + # episode.save() + # return True def show_manage_players_dialog(builder: Gtk.Builder, title: str): @@ -106,86 +102,88 @@ def show_manage_drinks_dialog(builder: Gtk.Builder): dialog.hide() -def show_edit_death_dialog(builder: Gtk.Builder, episode_id: int, death: sql.Death=None): - """Show a dialog for editing or creating death events. - :param builder: A Gtk.Builder object - :param episode_id: ID to witch the death event belongs to - :param death: (Optional) Death event witch should be edited - :return: Gtk.ResponseType of the dialog - """ - dialog = builder.get_object("edit_death_dialog") # type: Gtk.Dialog - dialog.set_transient_for(builder.get_object("main_window")) - with sql.db.atomic(): - if death: - index = util.get_index_of_combo_model(builder.get_object('edit_death_enemy_combo'), 0, death.enemy.id) - builder.get_object('edit_death_enemy_combo').set_active(index) - - # TODO Default drink should be set in config - default_drink = sql.Drink.get().name - store = builder.get_object('player_penalties_store') - store.clear() - for player in builder.get_object('episode_players_store'): - store.append([None, player[1], default_drink, player[0]]) - - # Run the dialog - result = dialog.run() - dialog.hide() - if result != Gtk.ResponseType.OK: - sql.db.rollback() - return result - - # Collect info from widgets and save to database - player_id = util.get_combo_value(builder.get_object('edit_death_player_combo'), 0) - enemy_id = util.get_combo_value(builder.get_object('edit_death_enemy_combo'), 3) - comment = builder.get_object('edit_death_comment_entry').get_text() - if not death: - death = sql.Death.create(episode=episode_id, player=player_id, enemy=enemy_id, info=comment) - - store = builder.get_object('player_penalties_store') - size = builder.get_object('edit_death_size_spin').get_value() - for entry in store: - drink_id = sql.Drink.get(sql.Drink.name == entry[2]) - sql.Penalty.create(size=size, player=entry[3], death=death.id, drink=drink_id) - - return result +def show_edit_death_dialog(builder: Gtk.Builder, episode_id: int, death): + pass + # """Show a dialog for editing or creating death events. + # :param builder: A Gtk.Builder object + # :param episode_id: ID to witch the death event belongs to + # :param death: (Optional) Death event witch should be edited + # :return: Gtk.ResponseType of the dialog + # """ + # dialog = builder.get_object("edit_death_dialog") # type: Gtk.Dialog + # dialog.set_transient_for(builder.get_object("main_window")) + # with sql.db.atomic(): + # if death: + # index = util.get_index_of_combo_model(builder.get_object('edit_death_enemy_combo'), 0, death.enemy.id) + # builder.get_object('edit_death_enemy_combo').set_active(index) + # + # # TODO Default drink should be set in config + # default_drink = sql.Drink.get().name + # store = builder.get_object('player_penalties_store') + # store.clear() + # for player in builder.get_object('episode_players_store'): + # store.append([None, player[1], default_drink, player[0]]) + # + # # Run the dialog + # result = dialog.run() + # dialog.hide() + # if result != Gtk.ResponseType.OK: + # sql.db.rollback() + # return result + # + # # Collect info from widgets and save to database + # player_id = util.get_combo_value(builder.get_object('edit_death_player_combo'), 0) + # enemy_id = util.get_combo_value(builder.get_object('edit_death_enemy_combo'), 3) + # comment = builder.get_object('edit_death_comment_entry').get_text() + # if not death: + # death = sql.Death.create(episode=episode_id, player=player_id, enemy=enemy_id, info=comment) + # + # store = builder.get_object('player_penalties_store') + # size = builder.get_object('edit_death_size_spin').get_value() + # for entry in store: + # drink_id = sql.Drink.get(sql.Drink.name == entry[2]) + # sql.Penalty.create(size=size, player=entry[3], death=death.id, drink=drink_id) + # + # return result -def show_edit_victory_dialog(builder: Gtk.Builder, episode_id: int, victory: sql.Victory=None): - """Show a dialog for editing or creating victory events. - :param builder: A Gtk.Builder object - :param episode_id: ID to witch the victory event belongs to - :param victory: (Optional) Victory event witch should be edited - :return: Gtk.ResponseType of the dialog - """ - dialog = builder.get_object("edit_victory_dialog") # type: Gtk.Dialog - dialog.set_transient_for(builder.get_object("main_window")) - with sql.db.atomic(): - if victory: - infos = [['edit_victory_player_combo', victory.player.id], - ['edit_victory_enemy_combo', victory.enemy.id]] - for info in infos: - combo = builder.get_object(info[0]) - index = util.get_index_of_combo_model(combo, 0, info[1]) - combo.set_active(index) - builder.get_object('victory_comment_entry').set_text(victory.info) - - # Run the dialog - result = dialog.run() - dialog.hide() - if result != Gtk.ResponseType.OK: - sql.db.rollback() - return result - - # Collect info from widgets and save to database - player_id = util.get_combo_value(builder.get_object('edit_victory_player_combo'), 0) - enemy_id = util.get_combo_value(builder.get_object('edit_victory_enemy_combo'), 3) - comment = builder.get_object('victory_comment_entry').get_text() - if not victory: - sql.Victory.create(episode=episode_id, player=player_id, enemy=enemy_id, info=comment) - else: - victory.player = player_id - victory.enemy = enemy_id - victory.info = comment - victory.save() - - return result +def show_edit_victory_dialog(builder: Gtk.Builder, episode_id: int, victory): + pass + # """Show a dialog for editing or creating victory events. + # :param builder: A Gtk.Builder object + # :param episode_id: ID to witch the victory event belongs to + # :param victory: (Optional) Victory event witch should be edited + # :return: Gtk.ResponseType of the dialog + # """ + # dialog = builder.get_object("edit_victory_dialog") # type: Gtk.Dialog + # dialog.set_transient_for(builder.get_object("main_window")) + # with sql.db.atomic(): + # if victory: + # infos = [['edit_victory_player_combo', victory.player.id], + # ['edit_victory_enemy_combo', victory.enemy.id]] + # for info in infos: + # combo = builder.get_object(info[0]) + # index = util.get_index_of_combo_model(combo, 0, info[1]) + # combo.set_active(index) + # builder.get_object('victory_comment_entry').set_text(victory.info) + # + # # Run the dialog + # result = dialog.run() + # dialog.hide() + # if result != Gtk.ResponseType.OK: + # sql.db.rollback() + # return result + # + # # Collect info from widgets and save to database + # player_id = util.get_combo_value(builder.get_object('edit_victory_player_combo'), 0) + # enemy_id = util.get_combo_value(builder.get_object('edit_victory_enemy_combo'), 3) + # comment = builder.get_object('victory_comment_entry').get_text() + # if not victory: + # sql.Victory.create(episode=episode_id, player=player_id, enemy=enemy_id, info=comment) + # else: + # victory.player = player_id + # victory.enemy = enemy_id + # victory.info = comment + # victory.save() + # + # return result diff --git a/dsst/dsst_gtk3/gtk_ui.py b/dsst/dsst_gtk3/gtk_ui.py index 1a2c364..6f9494a 100644 --- a/dsst/dsst_gtk3/gtk_ui.py +++ b/dsst/dsst_gtk3/gtk_ui.py @@ -1,13 +1,9 @@ import os - 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, client -import sql_func -import sql class GtkUi: @@ -30,41 +26,37 @@ class GtkUi: config = config['servers'][0] self.data_client = client.Access(config) self.data = {} + self.meta = {'connection': '{}:{}'.format(config.get('host'), config.get('port'))} + self.season_changed = True + self.ep_changed = False # Load base data and seasons self.initial_load() + self.update_status_bar_meta() def initial_load(self): - with util.handle_exception(Exception): + with util.network_operation(self): 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) + self.meta['database'] = self.data_client.send_request('load_db_meta') + reload.reload_base_data(self.ui, self) def reload(self): - 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) + if self.season_changed: + with util.network_operation(self): + season_id = self.get_selected_season_id() + self.data['episodes'] = self.data_client.send_request('load_episodes', season_id) + self.data['season_stats'] = self.data_client.send_request('load_season_stats', season_id) + reload.reload_episodes(self.ui, self) + reload.reload_season_stats(self) + self.season_changed = False - 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() + if self.ep_changed: + reload.reload_episode_stats(self) - def set_db_status_label(self, db_conf: dict): - self.ui.get_object('connection_label').set_text(f'{db_conf["user"]}@{db_conf["host"]}') - self.ui.get_object('db_label').set_text(f'{db_conf["db_name"]}') + def update_status_bar_meta(self): + self.ui.get_object('connection_label').set_text(self.meta.get('connection')) + self.ui.get_object('db_label').set_text(self.meta.get('database') or '') def get_selected_season_id(self) -> int: """Read ID of the selected season from the UI @@ -87,3 +79,7 @@ def main(): config = util.load_config(util.CONFIG_PATH) GtkUi(config) Gtk.main() + + +if __name__ == '__main__': + main() diff --git a/dsst/dsst_gtk3/handlers/base_data_handlers.py b/dsst/dsst_gtk3/handlers/base_data_handlers.py index 36c2d51..5efa887 100644 --- a/dsst/dsst_gtk3/handlers/base_data_handlers.py +++ b/dsst/dsst_gtk3/handlers/base_data_handlers.py @@ -1,4 +1,3 @@ -import sql from dsst_gtk3 import dialogs @@ -12,7 +11,7 @@ class BaseDataHandlers: def do_add_player(self, entry): if entry.get_text(): - sql.Player.create(name=entry.get_text()) + # sql.Player.create(name=entry.get_text()) entry.set_text('') self.app.reload() @@ -21,16 +20,16 @@ class BaseDataHandlers: def on_player_name_edited(self, _, index, value): row = self.app.ui.get_object('all_players_store')[index] - sql.Player.update(name=value)\ - .where(sql.Player.id == row[0])\ - .execute() + # sql.Player.update(name=value)\ + # .where(sql.Player.id == row[0])\ + # .execute() self.app.reload() def on_player_hex_edited(self, _, index, value): row = self.app.ui.get_object('all_players_store')[index] - sql.Player.update(hex_id=value)\ - .where(sql.Player.id == row[0])\ - .execute() + # sql.Player.update(hex_id=value)\ + # .where(sql.Player.id == row[0])\ + # .execute() self.app.reload() def do_add_drink(self, entry): @@ -41,14 +40,14 @@ class BaseDataHandlers: def on_drink_name_edited(self, _, index, value): row = self.app.ui.get_object('drink_store')[index] - sql.Drink.update(name=value)\ - .where(sql.Drink.id == row[0])\ - .execute() + # sql.Drink.update(name=value)\ + # .where(sql.Drink.id == row[0])\ + # .execute() self.app.reload() def on_drink_vol_edited(self, _, index, value): row = self.app.ui.get_object('drink_store')[index] - sql.Drink.update(vol=value) \ - .where(sql.Drink.id == row[0]) \ - .execute() + # sql.Drink.update(vol=value) \ + # .where(sql.Drink.id == row[0]) \ + # .execute() self.app.reload() \ No newline at end of file diff --git a/dsst/dsst_gtk3/handlers/death_handlers.py b/dsst/dsst_gtk3/handlers/death_handlers.py index fa4cbff..410b3a0 100644 --- a/dsst/dsst_gtk3/handlers/death_handlers.py +++ b/dsst/dsst_gtk3/handlers/death_handlers.py @@ -1,5 +1,4 @@ from gi.repository import Gtk - from dsst_gtk3 import dialogs diff --git a/dsst/dsst_gtk3/handlers/dialog_handlers.py b/dsst/dsst_gtk3/handlers/dialog_handlers.py index 1f34514..1224b50 100644 --- a/dsst/dsst_gtk3/handlers/dialog_handlers.py +++ b/dsst/dsst_gtk3/handlers/dialog_handlers.py @@ -1,4 +1,3 @@ -import sql from dsst_gtk3 import dialogs, util @@ -14,16 +13,16 @@ class DialogHandlers: player_id = util.get_combo_value(combo, 0) if player_id: self.app.ui.get_object('add_player_combo_box').set_active(-1) - player = sql.Player.get(sql.Player.id == player_id) + # player = sql.Player.get(sql.Player.id == player_id) store = self.app.ui.get_object('episode_players_store') - if not any(row[0] == player_id for row in store): - store.append([player_id, player.name, player.hex_id]) + # if not any(row[0] == player_id for row in store): + # store.append([player_id, player.name, player.hex_id]) def do_add_enemy(self, entry): if entry.get_text(): store = self.app.ui.get_object('enemy_season_store') - enemy = sql.Enemy.create(name=entry.get_text(), season=self.app.get_selected_season_id()) - store.append([enemy.name, False, 0, enemy.id]) + # enemy = sql.Enemy.create(name=entry.get_text(), season=self.app.get_selected_season_id()) + # store.append([enemy.name, False, 0, enemy.id]) entry.set_text('') def do_manage_drinks(self, *_): diff --git a/dsst/dsst_gtk3/handlers/handlers.py b/dsst/dsst_gtk3/handlers/handlers.py index 748d5ae..73c93b4 100644 --- a/dsst/dsst_gtk3/handlers/handlers.py +++ b/dsst/dsst_gtk3/handlers/handlers.py @@ -6,8 +6,6 @@ from dsst_gtk3.handlers.base_data_handlers import BaseDataHandlers from dsst_gtk3.handlers.dialog_handlers import DialogHandlers from dsst_gtk3.handlers.death_handlers import DeathHandlers from dsst_gtk3.handlers.victory_handlers import VictoryHandlers -import sql_func -import sql class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers, VictoryHandlers): @@ -35,5 +33,4 @@ class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers, @staticmethod def do_delete_database(*_): - sql_func.drop_tables() - sql_func.create_tables() + pass diff --git a/dsst/dsst_gtk3/handlers/season_handlers.py b/dsst/dsst_gtk3/handlers/season_handlers.py index 2d5bc6a..9734381 100644 --- a/dsst/dsst_gtk3/handlers/season_handlers.py +++ b/dsst/dsst_gtk3/handlers/season_handlers.py @@ -1,5 +1,4 @@ -from data_access import sql -from dsst_gtk3 import dialogs +from dsst_gtk3 import dialogs, gtk_ui class SeasonHandlers: @@ -10,10 +9,10 @@ class SeasonHandlers: def do_add_season(self, *_): name = dialogs.enter_string_dialog(self.app.ui, 'Name for the new Season') if name: - sql.Season.create(game_name=name, number=1) self.app.reload() def do_season_selected(self, *_): + self.app.season_changed = True self.app.reload() def do_add_episode(self, *_): @@ -24,6 +23,7 @@ class SeasonHandlers: self.app.reload() def on_selected_episode_changed(self, *_): + self.app.ep_changed = True self.app.reload() def on_episode_double_click(self, *_): diff --git a/dsst/dsst_gtk3/handlers/victory_handlers.py b/dsst/dsst_gtk3/handlers/victory_handlers.py index a9a8001..8b1f9a5 100644 --- a/dsst/dsst_gtk3/handlers/victory_handlers.py +++ b/dsst/dsst_gtk3/handlers/victory_handlers.py @@ -1,5 +1,4 @@ from gi.repository import Gtk - from dsst_gtk3 import dialogs diff --git a/dsst/dsst_gtk3/reload.py b/dsst/dsst_gtk3/reload.py index d352e3b..e7d7d49 100644 --- a/dsst/dsst_gtk3/reload.py +++ b/dsst/dsst_gtk3/reload.py @@ -1,8 +1,5 @@ from collections import Counter - from gi.repository import Gtk - -from data_access import sql, sql_func from dsst_gtk3 import util, gtk_ui @@ -46,45 +43,35 @@ def reload_episodes(builder: Gtk.Builder, app: 'gtk_ui.GtkUi'): selection.select_path(selected_paths[0]) -def reload_season_stats(builder: Gtk.Builder, app: 'gtk_ui.GtkUi', season_id: int): +def reload_season_stats(app: 'gtk_ui.GtkUi'): """Load statistic data for selected season - :param builder: Gtk.Builder with loaded UI :param app: GtkUi instance - :param season_id: ID of the season for witch to load data """ - player_stats = {} - for episode in sql_func.get_episodes_for_season(season_id): - for player in episode.players: - player_stats[player.name] = [sql_func.get_player_deaths_for_season(season_id, player.id), - sql_func.get_player_victories_for_season(season_id, player.id)] - store = builder.get_object('player_season_store') + season_stats = app.data.get('season_stats') + # Load player kill/death data + store = app.ui.get_object('player_season_store') store.clear() - for name, stats in player_stats.items(): - store.append([name, stats[0], stats[1]]) + for player_name, kills, deaths in season_stats.player_kd: + store.append([player_name, deaths, kills]) + # Load enemy stats for season - season = sql.Season.get(sql.Season.id == season_id) - enemy_stats = { - enemy.name: [False, len(sql.Death.select().where(sql.Death.enemy == enemy)), enemy.id] - for enemy in season.enemies} - store = builder.get_object('enemy_season_store') + store = app.ui.get_object('enemy_season_store') store.clear() - for name, stats in enemy_stats.items(): - store.append([name, stats[0], stats[1], stats[2]]) + for enemy_name, deaths, defeated, boss in season_stats.enemies: + store.append([enemy_name, defeated, deaths, boss]) -def reload_episode_stats(builder: Gtk.Builder, app: 'gtk_ui.GtkUi', episode_id: int): +def reload_episode_stats(app: 'gtk_ui.GtkUi'): """Reload all data that is dependant on a selected episode - :param builder: builder: Gtk.Builder with loaded UI :param app: app: GtkUi instance - :param episode_id: ID of the episode for witch to load data """ - episode = sql.Episode.get(sql.Episode.id == episode_id) - store = builder.get_object('episode_players_store') + episode = [ep for ep in app.data['episodes'] if ep.id == app.get_selected_episode_id()][0] + store = app.ui.get_object('episode_players_store') store.clear() for player in episode.players: store.append([player.id, player.name, player.hex_id]) # Reload death store for notebook view - store = builder.get_object('episode_deaths_store') + store = app.ui.get_object('episode_deaths_store') store.clear() for death in episode.deaths: penalties = [x.drink.name for x in death.penalties] @@ -92,28 +79,28 @@ def reload_episode_stats(builder: Gtk.Builder, app: 'gtk_ui.GtkUi', episode_id: penalty_string = ', '.join(penalties) store.append([death.id, death.player.name, death.enemy.name, penalty_string]) # Reload victory store for notebook view - store = builder.get_object('episode_victories_store') + store = app.ui.get_object('episode_victories_store') store.clear() for victory in episode.victories: store.append([victory.id, victory.player.name, victory.enemy.name, victory.info]) # Stat grid - builder.get_object('ep_stat_title').set_text('Stats for episode {}\n{}'.format(episode.number, episode.name)) - builder.get_object('ep_death_count_label').set_text(str(len(episode.deaths))) + app.ui.get_object('ep_stat_title').set_text('Stats for episode {}\n{}'.format(episode.number, episode.name)) + app.ui.get_object('ep_death_count_label').set_text(str(len(episode.deaths))) drink_count = sum(len(death.penalties) for death in episode.deaths) - builder.get_object('ep_drinks_label').set_text(str(drink_count)) - builder.get_object('ep_player_drinks_label').set_text(str(len(episode.deaths))) + app.ui.get_object('ep_drinks_label').set_text(str(drink_count)) + app.ui.get_object('ep_player_drinks_label').set_text(str(len(episode.deaths))) dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths) l_booze = round(dl_booze / 10, 2) - builder.get_object('ep_booze_label').set_text('{}l'.format(l_booze)) + app.ui.get_object('ep_booze_label').set_text('{}l'.format(l_booze)) dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths) ml_booze = round(dl_booze * 10, 0) - builder.get_object('ep_player_booze_label').set_text('{}ml'.format(ml_booze)) + app.ui.get_object('ep_player_booze_label').set_text('{}ml'.format(ml_booze)) enemy_list = [death.enemy.name for death in episode.deaths] sorted_list = Counter(enemy_list).most_common(1) if sorted_list: enemy_name, deaths = sorted_list[0] - builder.get_object('ep_enemy_name_label').set_text(f'{enemy_name} ({deaths} Deaths)') + app.ui.get_object('ep_enemy_name_label').set_text(f'{enemy_name} ({deaths} Deaths)') def fill_list_store(store: Gtk.ListStore, models: list): diff --git a/dsst/dsst_gtk3/resources/glade/window.glade b/dsst/dsst_gtk3/resources/glade/window.glade index 9312066..02d2131 100644 --- a/dsst/dsst_gtk3/resources/glade/window.glade +++ b/dsst/dsst_gtk3/resources/glade/window.glade @@ -1,5 +1,5 @@ - + @@ -366,8 +366,8 @@ - - + + @@ -2264,6 +2264,7 @@ Name + True True True 0 @@ -2345,7 +2346,9 @@ + True Name + True True True 0 @@ -2360,6 +2363,7 @@ + True Deaths True True @@ -2367,12 +2371,28 @@ - 1 2 + + + 40 + Boss + True + True + 3 + + + checkmark + + + 3 + + + + @@ -2417,6 +2437,7 @@ Name + True diff --git a/dsst/dsst_gtk3/util.py b/dsst/dsst_gtk3/util.py index 39d921e..25d5142 100644 --- a/dsst/dsst_gtk3/util.py +++ b/dsst/dsst_gtk3/util.py @@ -5,7 +5,8 @@ import json import os from contextlib import contextmanager from gi.repository import Gtk -from typing import Callable, Type +from typing import Callable +from dsst_gtk3 import gtk_ui from zipfile import ZipFile CONFIG_PATH = os.path.join(os.path.expanduser('~'), '.config', 'dsst', 'config.json') @@ -32,14 +33,19 @@ def block_handler(widget: 'Gtk.Widget', handler_func: Callable): @contextmanager -def handle_exception(exception: 'Type[Exception]'): +def network_operation(app: 'gtk_ui.GtkUi'): """Run operation in try/except block and display exception in a dialog :param exception: """ + app.ui.get_object('status_bar').push(0, 'Connecting to server') try: yield - except exception as e: + except Exception as e: print(e) + app.ui.get_object('status_bar').push(0, str(e)) + else: + app.ui.get_object('status_bar').push(0, '') + def get_combo_value(combo, index: int): diff --git a/dsst/dsst_server/__main__.py b/dsst/dsst_server/__main__.py new file mode 100644 index 0000000..267e28f --- /dev/null +++ b/dsst/dsst_server/__main__.py @@ -0,0 +1,11 @@ +import os.path +import sys + +# Add current directory to python path +path = os.path.realpath(os.path.abspath(__file__)) +sys.path.insert(0, os.path.dirname(os.path.dirname(path))) + +from dsst_server import server + +if __name__ == '__main__': + server.main() diff --git a/dsst/dsst_server/data_access/mapping.py b/dsst/dsst_server/data_access/mapping.py index e8052a7..ceeb1dc 100644 --- a/dsst/dsst_server/data_access/mapping.py +++ b/dsst/dsst_server/data_access/mapping.py @@ -3,10 +3,21 @@ from common import models def map_base_fields(cls, db_model): + """Automatically map fields of db models to common models + :param cls: common.models class to create + :param db_model: database model from which to map + :return: An common.models object + """ model = cls() attrs = [attr for attr in db_model._meta.fields] for attr in attrs: - setattr(model, attr, getattr(db_model, attr)) + db_attr = getattr(db_model, attr) + # Check if the attribute is an relation to another db model + # In that case just take its id + if hasattr(db_attr, 'id'): + setattr(model, attr, getattr(db_attr, 'id')) + else: + setattr(model, attr, getattr(db_model, attr)) return model diff --git a/dsst/dsst_server/data_access/sql.py b/dsst/dsst_server/data_access/sql.py index 16052c2..cd9391c 100644 --- a/dsst/dsst_server/data_access/sql.py +++ b/dsst/dsst_server/data_access/sql.py @@ -5,8 +5,12 @@ Example: from sql import Episode query = Episode.select().where(Episode.name == 'MyName') """ - -from peewee import * +import sys +try: + from peewee import * +except ImportError: + print('peewee package not installed') + sys.exit(0) db = MySQLDatabase(None) @@ -56,7 +60,7 @@ class Drink(Model): class Enemy(Model): id = AutoField() name = CharField() - optional = BooleanField() + boss = BooleanField() season = ForeignKeyField(Season, backref='enemies') class Meta: diff --git a/dsst/dsst_server/data_access/sql_func.py b/dsst/dsst_server/data_access/sql_func.py index dd12793..0166bdc 100644 --- a/dsst/dsst_server/data_access/sql_func.py +++ b/dsst/dsst_server/data_access/sql_func.py @@ -1,7 +1,7 @@ """ This module contains shorthand functions for common queries to ease access from the UI """ -from data_access.sql import * +from dsst_server.data_access import sql def get_episodes_for_season(season_id: int) -> list: @@ -10,8 +10,8 @@ def get_episodes_for_season(season_id: int) -> list: :return: List of sql.Episode or empty list """ try: - return list(Season.get(Season.id == season_id).episodes) - except Episode.DoesNotExist: + return list(sql.Season.get(sql.Season.id == season_id).episodes) + except sql.Episode.DoesNotExist: return [] @@ -22,7 +22,7 @@ def get_player_deaths_for_season(season_id: int, player_id: int) -> int: :return: Number of deaths of the player in the season """ deaths = 0 - for episode in list(Season.get(Season.id == season_id).episodes): + for episode in list(sql.Season.get(sql.Season.id == season_id).episodes): deaths = deaths + len([death for death in list(episode.deaths) if death.player.id == player_id]) return deaths @@ -34,19 +34,33 @@ def get_player_victories_for_season(season_id: int, player_id: int) -> int: :return: Number of all victories of the player in the season """ victories = 0 - for episode in list(Season.get(Season.id == season_id).episodes): + for episode in list(sql.Season.get(sql.Season.id == season_id).episodes): victories = victories + len([vic for vic in list(episode.victories) if vic.player.id == player_id]) return victories +def players_for_season(season_id: int) -> set: + season_eps = list(sql.Season.get(sql.Season.id == season_id).episodes) + players = set() + for ep in season_eps: + players.update([player for player in ep.players]) + return players + + +def enemy_attempts(enemy_id: int) -> int: + return sql.Death.select().where(sql.Death.enemy == enemy_id).count() + + def create_tables(): """Create all database tables""" - models = [Season, Episode, Player, Drink, Enemy, Death, Victory, Penalty, Episode.players.get_through_model()] + models = [sql.Season, sql.Episode, sql.Player, sql.Drink, sql.Enemy, sql.Death, sql.Victory, sql.Penalty, + sql.Episode.players.get_through_model()] for model in models: model.create_table() def drop_tables(): """Drop all data in database""" - models = [Season, Episode, Player, Drink, Enemy, Death, Victory, Penalty, Episode.players.get_through_model()] - db.drop_tables(models) \ No newline at end of file + models = [sql.Season, sql.Episode, sql.Player, sql.Drink, sql.Enemy, sql.Death, sql.Victory, sql.Penalty, + sql.Episode.players.get_through_model()] + sql.db.drop_tables(models) \ No newline at end of file diff --git a/dsst/dsst_server/read_functions.py b/dsst/dsst_server/read_functions.py index 7e79272..6a95f82 100644 --- a/dsst/dsst_server/read_functions.py +++ b/dsst/dsst_server/read_functions.py @@ -4,6 +4,9 @@ from playhouse import shortcuts class ReadFunctions: + @staticmethod + def load_db_meta(*_): + return sql.db.database @staticmethod def load_seasons(*_): @@ -25,4 +28,20 @@ class ReadFunctions: @staticmethod def load_drinks(*_): - return [mapping.db_to_drink(drink) for drink in sql.Drink.select()] \ No newline at end of file + return [mapping.db_to_drink(drink) for drink in sql.Drink.select()] + + @staticmethod + def load_season_stats(season_id, *_): + season = sql.Season.get(sql.Season.id == season_id) + players = sql_func.players_for_season(season_id) + model = models.SeasonStats() + model.player_kd = [(player.name, + sql_func.get_player_victories_for_season(season_id, player.id), + sql_func.get_player_deaths_for_season(season_id, player.id)) + for player in players] + model.enemies = [(enemy.name, + sql_func.enemy_attempts(enemy.id), + sql.Victory.select().where(sql.Victory.enemy == enemy.id).exists(), + enemy.boss) + for enemy in season.enemies] + return model diff --git a/dsst/dsst_server/server.py b/dsst/dsst_server/server.py index d48aedb..5a0794d 100644 --- a/dsst/dsst_server/server.py +++ b/dsst/dsst_server/server.py @@ -1,3 +1,4 @@ +import json import pickle import socket @@ -8,7 +9,7 @@ import os from common import util, models from dsst_server import read_functions, write_functions, tokens from dsst_server.func_proxy import FunctionProxy -from dsst_server.data_access import sql +from dsst_server.data_access import sql, sql_func PORT = 12345 HOST = socket.gethostname() @@ -16,16 +17,18 @@ BUFFER_SIZE = 1024 class DsstServer: - def __init__(self): + def __init__(self, config={}): self.socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print('Created socket') self.socket_server.bind((HOST, PORT)) - print(f'Bound socket to {PORT} on host {HOST}') + print('Bound socket to {} on host {}'.format(PORT, HOST)) # Initialize database - sql.db.init('dsst', user='dsst', password='dsst') - print(f'Database initialized ({sql.db.database})') + db_config = config.get('database') + sql.db.init(db_config.get('db_name'), user=db_config.get('user'), password=db_config.get('password')) + sql_func.create_tables() + print('Database initialized ({})'.format(sql.db.database)) # Load access tokens and map them to their allowed methods read_actions = util.list_class_methods(read_functions.ReadFunctions) @@ -35,7 +38,7 @@ class DsstServer: '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()}') + print('Loaded auth tokens: {}'.format(self.tokens.keys())) def run(self): self.socket_server.listen(5) @@ -44,15 +47,15 @@ class DsstServer: while True: client, address = self.socket_server.accept() try: - print(f'Connection from {address}') + print('Connection from {}'.format(address)) data = util.recv_msg(client) request = pickle.loads(data) - print(f'Request: {request}') + print('Request: {}'.format(request)) # 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})') + print('Rejected request from {}. Auth token invalid ({})'.format(address, token)) continue # Check read functions action_name = request.get('action') @@ -61,14 +64,14 @@ class DsstServer: try: value = action(request.get('args')) except Exception as e: - response = {'success': False, 'message': f'Exception was thrown on server.\n{e}'} + response = {'success': False, 'message': 'Exception was thrown on server.\n{}'.format(e)} util.send_msg(client, pickle.dumps(response)) raise response = {'success': True, 'data': value} util.send_msg(client, pickle.dumps(response)) continue else: - msg = f'Action does not exist on server ({request.get("action")})' + msg = 'Action does not exist on server ({})'.format(request.get('action')) util.send_msg(client, pickle.dumps({'success': False, 'message': msg})) except Exception as e: print(e) @@ -77,8 +80,14 @@ class DsstServer: print('Connection to client closed') -if __name__ == '__main__': - server = DsstServer() +def load_config(config_path: str) -> dict: + with open(config_path) as config_file: + return json.load(config_file) + + +def main(): + config = os.path.join(os.path.expanduser('~'), '.config', 'dsst', 'server.json') + server = DsstServer(load_config(config)) try: server.run() except KeyboardInterrupt: