Update build script for client/server use.

This commit is contained in:
luxick
2018-03-07 15:26:22 +01:00
parent 8b0422b1b0
commit e5d0d92de2
20 changed files with 382 additions and 264 deletions

View File

@@ -3,16 +3,59 @@ Package application using zipapp into an executable zip archive
""" """
import os import os
import zipapp import zipapp
import sys
import shutil
INTERPRETER = '/usr/bin/env python3' INTERPRETER = '/usr/bin/env python3'
SOURCE_PATH = 'dsst'
TARGET_FILENAME = 'dsst'
# The bundled file should be placed into the build directory CLIENT_VERSION = '0.1'
target_path = os.path.join(os.path.dirname(__file__), 'build') 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 # Make sure it exists
if not os.path.isdir(target_path): if not os.path.isdir(BUILD_PATH):
os.mkdir(target_path) os.mkdir(BUILD_PATH)
target = os.path.join(target_path, TARGET_FILENAME)
# Create archive
zipapp.create_archive(source=SOURCE_PATH, target=target, interpreter=INTERPRETER) 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]()

View File

@@ -45,7 +45,7 @@ class Enemy:
def __init__(self, arg={}): def __init__(self, arg={}):
self.id = arg.get('id') self.id = arg.get('id')
self.name = arg.get('name') self.name = arg.get('name')
self.optional = arg.get('optional') self.boss = arg.get('boss')
self.season = arg.get('season') self.season = arg.get('season')
@@ -74,4 +74,10 @@ class Victory:
self.info = arg.get('info') self.info = arg.get('info')
self.player = arg.get('player') self.player = arg.get('player')
self.enemy = arg.get('enemy') self.enemy = arg.get('enemy')
self.episode = arg.get('episode') self.episode = arg.get('episode')
class SeasonStats:
def __init__(self, arg={}):
self.player_kd = arg.get('player_kd')
self.enemies = arg.get('enemies')

View File

@@ -3,9 +3,9 @@ import sys
# Add current directory to python path # Add current directory to python path
path = os.path.realpath(os.path.abspath(__file__)) 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__': if __name__ == '__main__':
gtk_ui.main() gtk_ui.main()

View File

@@ -1,12 +1,7 @@
""" """
This module contains UI functions for displaying different dialogs This module contains UI functions for displaying different dialogs
""" """
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk 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: 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 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 """ Shows a dialog to edit an episode
:param builder: GtkBuilder with loaded 'dialogs.glade' :param builder: GtkBuilder with loaded 'dialogs.glade'
:param title: Title of the dialog window :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 :param episode: (Optional) Existing episode to edit
:return True if changes where saved False if discarded :return True if changes where saved False if discarded
""" """
# Set up the dialog pass
dialog = builder.get_object("edit_episode_dialog") # type: Gtk.Dialog # # Set up the dialog
dialog.set_transient_for(builder.get_object("main_window")) # dialog = builder.get_object("edit_episode_dialog") # type: Gtk.Dialog
dialog.set_title(title) # dialog.set_transient_for(builder.get_object("main_window"))
with sql.db.atomic(): # dialog.set_title(title)
if not episode: # with sql.db.atomic():
nxt_number = len(sql.Season.get_by_id(season_id).episodes) + 1 # if not episode:
episode = sql.Episode.create(seq_number=nxt_number, number=nxt_number, date=datetime.today(), # nxt_number = len(sql.Season.get_by_id(season_id).episodes) + 1
season=season_id) # episode = sql.Episode.create(seq_number=nxt_number, number=nxt_number, date=datetime.today(),
# Set episode number # season=season_id)
builder.get_object("episode_no_spin_button").set_value(episode.number) # # Set episode number
# Set episode date # builder.get_object("episode_no_spin_button").set_value(episode.number)
builder.get_object('episode_calendar').select_month(episode.date.month, episode.date.year) # # Set episode date
builder.get_object('episode_calendar').select_day(episode.date.day) # builder.get_object('episode_calendar').select_month(episode.date.month, episode.date.year)
# Set participants for the episode # builder.get_object('episode_calendar').select_day(episode.date.day)
builder.get_object('episode_players_store').clear() # # Set participants for the episode
for player in episode.players: # builder.get_object('episode_players_store').clear()
builder.get_object('episode_players_store').append([player.id, player.name, player.hex_id]) # for player in episode.players:
# builder.get_object('episode_players_store').append([player.id, player.name, player.hex_id])
result = dialog.run() #
dialog.hide() # result = dialog.run()
# dialog.hide()
if result != Gtk.ResponseType.OK: #
sql.db.rollback() # if result != Gtk.ResponseType.OK:
return False # sql.db.rollback()
# return False
# Save all changes to Database #
player_ids = [row[0] for row in builder.get_object('episode_players_store')] # # Save all changes to Database
# Insert new Players # player_ids = [row[0] for row in builder.get_object('episode_players_store')]
episode.players = sql.Player.select().where(sql.Player.id << player_ids) # # Insert new Players
# Update Date of the Episode # episode.players = sql.Player.select().where(sql.Player.id << player_ids)
cal_value = builder.get_object('episode_calendar').get_date() # # Update Date of the Episode
selected_date = datetime(*cal_value).date() # cal_value = builder.get_object('episode_calendar').get_date()
episode.date = selected_date, # selected_date = datetime(*cal_value).date()
episode.number = int(builder.get_object("episode_no_spin_button").get_value()) # episode.date = selected_date,
episode.name = builder.get_object("episode_name_entry").get_text() # episode.number = int(builder.get_object("episode_no_spin_button").get_value())
episode.save() # episode.name = builder.get_object("episode_name_entry").get_text()
return True # episode.save()
# return True
def show_manage_players_dialog(builder: Gtk.Builder, title: str): def show_manage_players_dialog(builder: Gtk.Builder, title: str):
@@ -106,86 +102,88 @@ def show_manage_drinks_dialog(builder: Gtk.Builder):
dialog.hide() dialog.hide()
def show_edit_death_dialog(builder: Gtk.Builder, episode_id: int, death: sql.Death=None): def show_edit_death_dialog(builder: Gtk.Builder, episode_id: int, death):
"""Show a dialog for editing or creating death events. pass
:param builder: A Gtk.Builder object # """Show a dialog for editing or creating death events.
:param episode_id: ID to witch the death event belongs to # :param builder: A Gtk.Builder object
:param death: (Optional) Death event witch should be edited # :param episode_id: ID to witch the death event belongs to
:return: Gtk.ResponseType of the dialog # :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")) # dialog = builder.get_object("edit_death_dialog") # type: Gtk.Dialog
with sql.db.atomic(): # dialog.set_transient_for(builder.get_object("main_window"))
if death: # with sql.db.atomic():
index = util.get_index_of_combo_model(builder.get_object('edit_death_enemy_combo'), 0, death.enemy.id) # if death:
builder.get_object('edit_death_enemy_combo').set_active(index) # 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 # # TODO Default drink should be set in config
store = builder.get_object('player_penalties_store') # default_drink = sql.Drink.get().name
store.clear() # store = builder.get_object('player_penalties_store')
for player in builder.get_object('episode_players_store'): # store.clear()
store.append([None, player[1], default_drink, player[0]]) # for player in builder.get_object('episode_players_store'):
# store.append([None, player[1], default_drink, player[0]])
# Run the dialog #
result = dialog.run() # # Run the dialog
dialog.hide() # result = dialog.run()
if result != Gtk.ResponseType.OK: # dialog.hide()
sql.db.rollback() # if result != Gtk.ResponseType.OK:
return result # 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) # # Collect info from widgets and save to database
enemy_id = util.get_combo_value(builder.get_object('edit_death_enemy_combo'), 3) # player_id = util.get_combo_value(builder.get_object('edit_death_player_combo'), 0)
comment = builder.get_object('edit_death_comment_entry').get_text() # enemy_id = util.get_combo_value(builder.get_object('edit_death_enemy_combo'), 3)
if not death: # comment = builder.get_object('edit_death_comment_entry').get_text()
death = sql.Death.create(episode=episode_id, player=player_id, enemy=enemy_id, info=comment) # 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() # store = builder.get_object('player_penalties_store')
for entry in store: # size = builder.get_object('edit_death_size_spin').get_value()
drink_id = sql.Drink.get(sql.Drink.name == entry[2]) # for entry in store:
sql.Penalty.create(size=size, player=entry[3], death=death.id, drink=drink_id) # 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 #
# return result
def show_edit_victory_dialog(builder: Gtk.Builder, episode_id: int, victory: sql.Victory=None): def show_edit_victory_dialog(builder: Gtk.Builder, episode_id: int, victory):
"""Show a dialog for editing or creating victory events. pass
:param builder: A Gtk.Builder object # """Show a dialog for editing or creating victory events.
:param episode_id: ID to witch the victory event belongs to # :param builder: A Gtk.Builder object
:param victory: (Optional) Victory event witch should be edited # :param episode_id: ID to witch the victory event belongs to
:return: Gtk.ResponseType of the dialog # :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")) # dialog = builder.get_object("edit_victory_dialog") # type: Gtk.Dialog
with sql.db.atomic(): # dialog.set_transient_for(builder.get_object("main_window"))
if victory: # with sql.db.atomic():
infos = [['edit_victory_player_combo', victory.player.id], # if victory:
['edit_victory_enemy_combo', victory.enemy.id]] # infos = [['edit_victory_player_combo', victory.player.id],
for info in infos: # ['edit_victory_enemy_combo', victory.enemy.id]]
combo = builder.get_object(info[0]) # for info in infos:
index = util.get_index_of_combo_model(combo, 0, info[1]) # combo = builder.get_object(info[0])
combo.set_active(index) # index = util.get_index_of_combo_model(combo, 0, info[1])
builder.get_object('victory_comment_entry').set_text(victory.info) # combo.set_active(index)
# builder.get_object('victory_comment_entry').set_text(victory.info)
# Run the dialog #
result = dialog.run() # # Run the dialog
dialog.hide() # result = dialog.run()
if result != Gtk.ResponseType.OK: # dialog.hide()
sql.db.rollback() # if result != Gtk.ResponseType.OK:
return result # 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) # # Collect info from widgets and save to database
enemy_id = util.get_combo_value(builder.get_object('edit_victory_enemy_combo'), 3) # player_id = util.get_combo_value(builder.get_object('edit_victory_player_combo'), 0)
comment = builder.get_object('victory_comment_entry').get_text() # enemy_id = util.get_combo_value(builder.get_object('edit_victory_enemy_combo'), 3)
if not victory: # comment = builder.get_object('victory_comment_entry').get_text()
sql.Victory.create(episode=episode_id, player=player_id, enemy=enemy_id, info=comment) # if not victory:
else: # sql.Victory.create(episode=episode_id, player=player_id, enemy=enemy_id, info=comment)
victory.player = player_id # else:
victory.enemy = enemy_id # victory.player = player_id
victory.info = comment # victory.enemy = enemy_id
victory.save() # victory.info = comment
# victory.save()
return result #
# return result

View File

@@ -1,13 +1,9 @@
import os import os
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
from dsst_gtk3.handlers import handlers from dsst_gtk3.handlers import handlers
from dsst_gtk3 import util, reload, client from dsst_gtk3 import util, reload, client
import sql_func
import sql
class GtkUi: class GtkUi:
@@ -30,41 +26,37 @@ class GtkUi:
config = config['servers'][0] config = config['servers'][0]
self.data_client = client.Access(config) self.data_client = client.Access(config)
self.data = {} 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 # Load base data and seasons
self.initial_load() self.initial_load()
self.update_status_bar_meta()
def initial_load(self): 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['players'] = self.data_client.send_request('load_players')
self.data['drinks'] = self.data_client.send_request('load_drinks') self.data['drinks'] = self.data_client.send_request('load_drinks')
self.data['seasons'] = self.data_client.send_request('load_seasons') 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): def reload(self):
with util.handle_exception(Exception): if self.season_changed:
self.data['episodes'] = self.data_client.send_request('load_episodes', self.get_selected_season_id()) with util.network_operation(self):
reload.reload_episodes(self.ui, self) season_id = self.get_selected_season_id()
pass self.data['episodes'] = self.data_client.send_request('load_episodes', season_id)
# reload.reload_base_data(self.ui, self) self.data['season_stats'] = self.data_client.send_request('load_season_stats', season_id)
# season_id = self.get_selected_season_id() reload.reload_episodes(self.ui, self)
# if season_id: reload.reload_season_stats(self)
# reload.reload_episodes(self.ui, self, season_id) self.season_changed = False
# 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): if self.ep_changed:
try: reload.reload_episode_stats(self)
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): def update_status_bar_meta(self):
self.ui.get_object('connection_label').set_text(f'{db_conf["user"]}@{db_conf["host"]}') self.ui.get_object('connection_label').set_text(self.meta.get('connection'))
self.ui.get_object('db_label').set_text(f'{db_conf["db_name"]}') self.ui.get_object('db_label').set_text(self.meta.get('database') or '')
def get_selected_season_id(self) -> int: def get_selected_season_id(self) -> int:
"""Read ID of the selected season from the UI """Read ID of the selected season from the UI
@@ -87,3 +79,7 @@ def main():
config = util.load_config(util.CONFIG_PATH) config = util.load_config(util.CONFIG_PATH)
GtkUi(config) GtkUi(config)
Gtk.main() Gtk.main()
if __name__ == '__main__':
main()

View File

@@ -1,4 +1,3 @@
import sql
from dsst_gtk3 import dialogs from dsst_gtk3 import dialogs
@@ -12,7 +11,7 @@ class BaseDataHandlers:
def do_add_player(self, entry): def do_add_player(self, entry):
if entry.get_text(): if entry.get_text():
sql.Player.create(name=entry.get_text()) # sql.Player.create(name=entry.get_text())
entry.set_text('') entry.set_text('')
self.app.reload() self.app.reload()
@@ -21,16 +20,16 @@ class BaseDataHandlers:
def on_player_name_edited(self, _, index, value): def on_player_name_edited(self, _, index, value):
row = self.app.ui.get_object('all_players_store')[index] row = self.app.ui.get_object('all_players_store')[index]
sql.Player.update(name=value)\ # sql.Player.update(name=value)\
.where(sql.Player.id == row[0])\ # .where(sql.Player.id == row[0])\
.execute() # .execute()
self.app.reload() self.app.reload()
def on_player_hex_edited(self, _, index, value): def on_player_hex_edited(self, _, index, value):
row = self.app.ui.get_object('all_players_store')[index] row = self.app.ui.get_object('all_players_store')[index]
sql.Player.update(hex_id=value)\ # sql.Player.update(hex_id=value)\
.where(sql.Player.id == row[0])\ # .where(sql.Player.id == row[0])\
.execute() # .execute()
self.app.reload() self.app.reload()
def do_add_drink(self, entry): def do_add_drink(self, entry):
@@ -41,14 +40,14 @@ class BaseDataHandlers:
def on_drink_name_edited(self, _, index, value): def on_drink_name_edited(self, _, index, value):
row = self.app.ui.get_object('drink_store')[index] row = self.app.ui.get_object('drink_store')[index]
sql.Drink.update(name=value)\ # sql.Drink.update(name=value)\
.where(sql.Drink.id == row[0])\ # .where(sql.Drink.id == row[0])\
.execute() # .execute()
self.app.reload() self.app.reload()
def on_drink_vol_edited(self, _, index, value): def on_drink_vol_edited(self, _, index, value):
row = self.app.ui.get_object('drink_store')[index] row = self.app.ui.get_object('drink_store')[index]
sql.Drink.update(vol=value) \ # sql.Drink.update(vol=value) \
.where(sql.Drink.id == row[0]) \ # .where(sql.Drink.id == row[0]) \
.execute() # .execute()
self.app.reload() self.app.reload()

View File

@@ -1,5 +1,4 @@
from gi.repository import Gtk from gi.repository import Gtk
from dsst_gtk3 import dialogs from dsst_gtk3 import dialogs

View File

@@ -1,4 +1,3 @@
import sql
from dsst_gtk3 import dialogs, util from dsst_gtk3 import dialogs, util
@@ -14,16 +13,16 @@ class DialogHandlers:
player_id = util.get_combo_value(combo, 0) player_id = util.get_combo_value(combo, 0)
if player_id: if player_id:
self.app.ui.get_object('add_player_combo_box').set_active(-1) 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') store = self.app.ui.get_object('episode_players_store')
if not any(row[0] == player_id for row in store): # if not any(row[0] == player_id for row in store):
store.append([player_id, player.name, player.hex_id]) # store.append([player_id, player.name, player.hex_id])
def do_add_enemy(self, entry): def do_add_enemy(self, entry):
if entry.get_text(): if entry.get_text():
store = self.app.ui.get_object('enemy_season_store') store = self.app.ui.get_object('enemy_season_store')
enemy = sql.Enemy.create(name=entry.get_text(), season=self.app.get_selected_season_id()) # enemy = sql.Enemy.create(name=entry.get_text(), season=self.app.get_selected_season_id())
store.append([enemy.name, False, 0, enemy.id]) # store.append([enemy.name, False, 0, enemy.id])
entry.set_text('') entry.set_text('')
def do_manage_drinks(self, *_): def do_manage_drinks(self, *_):

View File

@@ -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.dialog_handlers import DialogHandlers
from dsst_gtk3.handlers.death_handlers import DeathHandlers from dsst_gtk3.handlers.death_handlers import DeathHandlers
from dsst_gtk3.handlers.victory_handlers import VictoryHandlers from dsst_gtk3.handlers.victory_handlers import VictoryHandlers
import sql_func
import sql
class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers, VictoryHandlers): class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers, VictoryHandlers):
@@ -35,5 +33,4 @@ class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers,
@staticmethod @staticmethod
def do_delete_database(*_): def do_delete_database(*_):
sql_func.drop_tables() pass
sql_func.create_tables()

View File

@@ -1,5 +1,4 @@
from data_access import sql from dsst_gtk3 import dialogs, gtk_ui
from dsst_gtk3 import dialogs
class SeasonHandlers: class SeasonHandlers:
@@ -10,10 +9,10 @@ class SeasonHandlers:
def do_add_season(self, *_): def do_add_season(self, *_):
name = dialogs.enter_string_dialog(self.app.ui, 'Name for the new Season') name = dialogs.enter_string_dialog(self.app.ui, 'Name for the new Season')
if name: if name:
sql.Season.create(game_name=name, number=1)
self.app.reload() self.app.reload()
def do_season_selected(self, *_): def do_season_selected(self, *_):
self.app.season_changed = True
self.app.reload() self.app.reload()
def do_add_episode(self, *_): def do_add_episode(self, *_):
@@ -24,6 +23,7 @@ class SeasonHandlers:
self.app.reload() self.app.reload()
def on_selected_episode_changed(self, *_): def on_selected_episode_changed(self, *_):
self.app.ep_changed = True
self.app.reload() self.app.reload()
def on_episode_double_click(self, *_): def on_episode_double_click(self, *_):

View File

@@ -1,5 +1,4 @@
from gi.repository import Gtk from gi.repository import Gtk
from dsst_gtk3 import dialogs from dsst_gtk3 import dialogs

View File

@@ -1,8 +1,5 @@
from collections import Counter from collections import Counter
from gi.repository import Gtk from gi.repository import Gtk
from data_access import sql, sql_func
from dsst_gtk3 import util, gtk_ui 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]) 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 """Load statistic data for selected season
:param builder: Gtk.Builder with loaded UI
:param app: GtkUi instance :param app: GtkUi instance
:param season_id: ID of the season for witch to load data
""" """
player_stats = {} season_stats = app.data.get('season_stats')
for episode in sql_func.get_episodes_for_season(season_id): # Load player kill/death data
for player in episode.players: store = app.ui.get_object('player_season_store')
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')
store.clear() store.clear()
for name, stats in player_stats.items(): for player_name, kills, deaths in season_stats.player_kd:
store.append([name, stats[0], stats[1]]) store.append([player_name, deaths, kills])
# Load enemy stats for season # Load enemy stats for season
season = sql.Season.get(sql.Season.id == season_id) store = app.ui.get_object('enemy_season_store')
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.clear() store.clear()
for name, stats in enemy_stats.items(): for enemy_name, deaths, defeated, boss in season_stats.enemies:
store.append([name, stats[0], stats[1], stats[2]]) 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 """Reload all data that is dependant on a selected episode
:param builder: builder: Gtk.Builder with loaded UI
:param app: app: GtkUi instance :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) episode = [ep for ep in app.data['episodes'] if ep.id == app.get_selected_episode_id()][0]
store = builder.get_object('episode_players_store') store = app.ui.get_object('episode_players_store')
store.clear() store.clear()
for player in episode.players: for player in episode.players:
store.append([player.id, player.name, player.hex_id]) store.append([player.id, player.name, player.hex_id])
# Reload death store for notebook view # Reload death store for notebook view
store = builder.get_object('episode_deaths_store') store = app.ui.get_object('episode_deaths_store')
store.clear() store.clear()
for death in episode.deaths: for death in episode.deaths:
penalties = [x.drink.name for x in death.penalties] 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) penalty_string = ', '.join(penalties)
store.append([death.id, death.player.name, death.enemy.name, penalty_string]) store.append([death.id, death.player.name, death.enemy.name, penalty_string])
# Reload victory store for notebook view # Reload victory store for notebook view
store = builder.get_object('episode_victories_store') store = app.ui.get_object('episode_victories_store')
store.clear() store.clear()
for victory in episode.victories: for victory in episode.victories:
store.append([victory.id, victory.player.name, victory.enemy.name, victory.info]) store.append([victory.id, victory.player.name, victory.enemy.name, victory.info])
# Stat grid # Stat grid
builder.get_object('ep_stat_title').set_text('Stats for episode {}\n{}'.format(episode.number, episode.name)) app.ui.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_death_count_label').set_text(str(len(episode.deaths)))
drink_count = sum(len(death.penalties) for death in episode.deaths) drink_count = sum(len(death.penalties) for death in episode.deaths)
builder.get_object('ep_drinks_label').set_text(str(drink_count)) app.ui.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_player_drinks_label').set_text(str(len(episode.deaths)))
dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths) dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths)
l_booze = round(dl_booze / 10, 2) 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) dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths)
ml_booze = round(dl_booze * 10, 0) 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] enemy_list = [death.enemy.name for death in episode.deaths]
sorted_list = Counter(enemy_list).most_common(1) sorted_list = Counter(enemy_list).most_common(1)
if sorted_list: if sorted_list:
enemy_name, deaths = sorted_list[0] 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): def fill_list_store(store: Gtk.ListStore, models: list):

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.3 --> <!-- Generated with glade 3.20.4 -->
<interface> <interface>
<requires lib="gtk+" version="3.20"/> <requires lib="gtk+" version="3.20"/>
<object class="GtkListStore" id="all_players_store"> <object class="GtkListStore" id="all_players_store">
@@ -366,8 +366,8 @@
<column type="gboolean"/> <column type="gboolean"/>
<!-- column-name attempts --> <!-- column-name attempts -->
<column type="gint"/> <column type="gint"/>
<!-- column-name enemy_id --> <!-- column-name optional -->
<column type="gint"/> <column type="gboolean"/>
</columns> </columns>
</object> </object>
<object class="GtkDialog" id="manage_enemies_dialog"> <object class="GtkDialog" id="manage_enemies_dialog">
@@ -2264,6 +2264,7 @@
<child> <child>
<object class="GtkTreeViewColumn"> <object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Name</property> <property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="clickable">True</property> <property name="clickable">True</property>
<property name="sort_indicator">True</property> <property name="sort_indicator">True</property>
<property name="sort_column_id">0</property> <property name="sort_column_id">0</property>
@@ -2345,7 +2346,9 @@
</child> </child>
<child> <child>
<object class="GtkTreeViewColumn"> <object class="GtkTreeViewColumn">
<property name="resizable">True</property>
<property name="title" translatable="yes">Name</property> <property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="clickable">True</property> <property name="clickable">True</property>
<property name="sort_indicator">True</property> <property name="sort_indicator">True</property>
<property name="sort_column_id">0</property> <property name="sort_column_id">0</property>
@@ -2360,6 +2363,7 @@
</child> </child>
<child> <child>
<object class="GtkTreeViewColumn"> <object class="GtkTreeViewColumn">
<property name="resizable">True</property>
<property name="title" translatable="yes">Deaths</property> <property name="title" translatable="yes">Deaths</property>
<property name="clickable">True</property> <property name="clickable">True</property>
<property name="sort_indicator">True</property> <property name="sort_indicator">True</property>
@@ -2367,12 +2371,28 @@
<child> <child>
<object class="GtkCellRendererText"/> <object class="GtkCellRendererText"/>
<attributes> <attributes>
<attribute name="strikethrough">1</attribute>
<attribute name="text">2</attribute> <attribute name="text">2</attribute>
</attributes> </attributes>
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="GtkTreeViewColumn">
<property name="fixed_width">40</property>
<property name="title" translatable="yes">Boss</property>
<property name="clickable">True</property>
<property name="sort_indicator">True</property>
<property name="sort_column_id">3</property>
<child>
<object class="GtkCellRendererPixbuf">
<property name="icon_name">checkmark</property>
</object>
<attributes>
<attribute name="visible">3</attribute>
</attributes>
</child>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>
@@ -2417,6 +2437,7 @@
<child> <child>
<object class="GtkTreeViewColumn"> <object class="GtkTreeViewColumn">
<property name="title" translatable="yes">Name</property> <property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<child> <child>
<object class="GtkCellRendererText"/> <object class="GtkCellRendererText"/>
<attributes> <attributes>

View File

@@ -5,7 +5,8 @@ import json
import os import os
from contextlib import contextmanager from contextlib import contextmanager
from gi.repository import Gtk from gi.repository import Gtk
from typing import Callable, Type from typing import Callable
from dsst_gtk3 import gtk_ui
from zipfile import ZipFile from zipfile import ZipFile
CONFIG_PATH = os.path.join(os.path.expanduser('~'), '.config', 'dsst', 'config.json') 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 @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 """Run operation in try/except block and display exception in a dialog
:param exception: :param exception:
""" """
app.ui.get_object('status_bar').push(0, 'Connecting to server')
try: try:
yield yield
except exception as e: except Exception as e:
print(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): def get_combo_value(combo, index: int):

View File

@@ -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()

View File

@@ -3,10 +3,21 @@ from common import models
def map_base_fields(cls, db_model): 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() model = cls()
attrs = [attr for attr in db_model._meta.fields] attrs = [attr for attr in db_model._meta.fields]
for attr in attrs: 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 return model

View File

@@ -5,8 +5,12 @@ Example:
from sql import Episode from sql import Episode
query = Episode.select().where(Episode.name == 'MyName') query = Episode.select().where(Episode.name == 'MyName')
""" """
import sys
from peewee import * try:
from peewee import *
except ImportError:
print('peewee package not installed')
sys.exit(0)
db = MySQLDatabase(None) db = MySQLDatabase(None)
@@ -56,7 +60,7 @@ class Drink(Model):
class Enemy(Model): class Enemy(Model):
id = AutoField() id = AutoField()
name = CharField() name = CharField()
optional = BooleanField() boss = BooleanField()
season = ForeignKeyField(Season, backref='enemies') season = ForeignKeyField(Season, backref='enemies')
class Meta: class Meta:

View File

@@ -1,7 +1,7 @@
""" """
This module contains shorthand functions for common queries to ease access from the UI 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: 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 :return: List of sql.Episode or empty list
""" """
try: try:
return list(Season.get(Season.id == season_id).episodes) return list(sql.Season.get(sql.Season.id == season_id).episodes)
except Episode.DoesNotExist: except sql.Episode.DoesNotExist:
return [] 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 :return: Number of deaths of the player in the season
""" """
deaths = 0 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]) deaths = deaths + len([death for death in list(episode.deaths) if death.player.id == player_id])
return deaths 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 :return: Number of all victories of the player in the season
""" """
victories = 0 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]) victories = victories + len([vic for vic in list(episode.victories) if vic.player.id == player_id])
return victories 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(): def create_tables():
"""Create all database 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: for model in models:
model.create_table() model.create_table()
def drop_tables(): def drop_tables():
"""Drop all data in database""" """Drop all data in database"""
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,
db.drop_tables(models) sql.Episode.players.get_through_model()]
sql.db.drop_tables(models)

View File

@@ -4,6 +4,9 @@ from playhouse import shortcuts
class ReadFunctions: class ReadFunctions:
@staticmethod
def load_db_meta(*_):
return sql.db.database
@staticmethod @staticmethod
def load_seasons(*_): def load_seasons(*_):
@@ -25,4 +28,20 @@ class ReadFunctions:
@staticmethod @staticmethod
def load_drinks(*_): def load_drinks(*_):
return [mapping.db_to_drink(drink) for drink in sql.Drink.select()] 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

View File

@@ -1,3 +1,4 @@
import json
import pickle import pickle
import socket import socket
@@ -8,7 +9,7 @@ import os
from common import util, models from common import util, models
from dsst_server import read_functions, write_functions, tokens from dsst_server import read_functions, write_functions, tokens
from dsst_server.func_proxy import FunctionProxy 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 PORT = 12345
HOST = socket.gethostname() HOST = socket.gethostname()
@@ -16,16 +17,18 @@ BUFFER_SIZE = 1024
class DsstServer: class DsstServer:
def __init__(self): def __init__(self, config={}):
self.socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('Created socket') print('Created socket')
self.socket_server.bind((HOST, PORT)) 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 # Initialize database
sql.db.init('dsst', user='dsst', password='dsst') db_config = config.get('database')
print(f'Database initialized ({sql.db.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 # Load access tokens and map them to their allowed methods
read_actions = util.list_class_methods(read_functions.ReadFunctions) read_actions = util.list_class_methods(read_functions.ReadFunctions)
@@ -35,7 +38,7 @@ class DsstServer:
'rw': read_actions + write_actions 'rw': read_actions + write_actions
} }
self.tokens = {token: parm_access[perms] for token, perms in tokens.TOKENS} 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): def run(self):
self.socket_server.listen(5) self.socket_server.listen(5)
@@ -44,15 +47,15 @@ class DsstServer:
while True: while True:
client, address = self.socket_server.accept() client, address = self.socket_server.accept()
try: try:
print(f'Connection from {address}') print('Connection from {}'.format(address))
data = util.recv_msg(client) data = util.recv_msg(client)
request = pickle.loads(data) request = pickle.loads(data)
print(f'Request: {request}') print('Request: {}'.format(request))
# Validate auth token in request # Validate auth token in request
token = request.get('auth_token') token = request.get('auth_token')
if token not in self.tokens: if token not in self.tokens:
util.send_msg(client, pickle.dumps({'success': False, 'message': 'Auth token invalid'})) 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 continue
# Check read functions # Check read functions
action_name = request.get('action') action_name = request.get('action')
@@ -61,14 +64,14 @@ class DsstServer:
try: try:
value = action(request.get('args')) value = action(request.get('args'))
except Exception as e: 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)) util.send_msg(client, pickle.dumps(response))
raise raise
response = {'success': True, 'data': value} response = {'success': True, 'data': value}
util.send_msg(client, pickle.dumps(response)) util.send_msg(client, pickle.dumps(response))
continue continue
else: 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})) util.send_msg(client, pickle.dumps({'success': False, 'message': msg}))
except Exception as e: except Exception as e:
print(e) print(e)
@@ -77,8 +80,14 @@ class DsstServer:
print('Connection to client closed') print('Connection to client closed')
if __name__ == '__main__': def load_config(config_path: str) -> dict:
server = DsstServer() 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: try:
server.run() server.run()
except KeyboardInterrupt: except KeyboardInterrupt: