Compare commits
6 Commits
client_ser
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb8646bae2 | ||
|
|
b3eca219ac | ||
|
|
def5ff5ea6 | ||
|
|
ad85a37d5e | ||
|
|
4b8d2421a5 | ||
|
|
b5df3289b4 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -91,3 +91,5 @@ ENV/
|
||||
|
||||
.idea
|
||||
install.txt
|
||||
Screenshots/
|
||||
.vscode
|
||||
4
build.py
4
build.py
@@ -9,8 +9,8 @@ import shutil
|
||||
|
||||
INTERPRETER = '/usr/bin/env python3'
|
||||
|
||||
CLIENT_VERSION = '0.1'
|
||||
SERVER_VERSION = '0.1'
|
||||
CLIENT_VERSION = '0.2'
|
||||
SERVER_VERSION = '0.2'
|
||||
|
||||
try:
|
||||
build_mode = sys.argv[1]
|
||||
|
||||
@@ -5,10 +5,26 @@ try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
from dsst_gtk3 import gtk_ui
|
||||
|
||||
|
||||
class Access:
|
||||
def __init__(self, conn_dict):
|
||||
def gui_handled(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
self.app.ui.get_object('status_bar').push(0, 'Connecting to server')
|
||||
try:
|
||||
yield func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.app.ui.get_object('status_bar').push(0, str(e))
|
||||
else:
|
||||
self.app.ui.get_object('status_bar').push(0, '')
|
||||
self.app.full_reload()
|
||||
|
||||
return wrapper
|
||||
|
||||
class DataClient:
|
||||
def __init__(self, app: 'gtk_ui.GtkUi', conn_dict):
|
||||
self.app = app
|
||||
self.host = conn_dict.get('host')
|
||||
self.port = conn_dict.get('port')
|
||||
self.buffer = conn_dict.get('buffer_size')
|
||||
@@ -31,10 +47,145 @@ class Access:
|
||||
soc.close()
|
||||
return message.get('data')
|
||||
|
||||
|
||||
|
||||
@gui_handled
|
||||
def update_enemy(self, enemy: 'models.Enemy'):
|
||||
self.send_request('update_enemy', enemy)
|
||||
|
||||
def update_player(self, player: 'models.Player'):
|
||||
self.send_request('update_player', player)
|
||||
|
||||
def update_drink(self, drink: 'models.Drink'):
|
||||
self.send_request('update_drink', drink)
|
||||
|
||||
def save_death(self, death: 'models.Death'):
|
||||
self.send_request('save_death', death)
|
||||
|
||||
def delete_death(self, death_id: int):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('delete_death', death_id)
|
||||
self.full_reload()
|
||||
|
||||
def save_victory(self, victory: 'models.Victory'):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('save_victory', victory)
|
||||
self.full_reload()
|
||||
|
||||
def delete_victory(self, victory_id: int):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('delete_victory', victory_id)
|
||||
self.full_reload()
|
||||
|
||||
def update_season(self, season: 'models.Season'):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('update_season', season)
|
||||
self.seasons.valid = False
|
||||
|
||||
def update_episode(self, episode: 'models.Episode'):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('update_episode', episode)
|
||||
self.episodes.valid = False
|
||||
self.season_stats.valid = False
|
||||
|
||||
if __name__ == '__main__':
|
||||
access = Access({'host': 'europa', 'port': 12345, 'buffer_size': 1024, 'auth_token': 'a'})
|
||||
access = DataClient({'host': 'europa', 'port': 12345, 'buffer_size': 1024, 'auth_token': 'a'})
|
||||
action = 'load_seasons'
|
||||
response = access.send_request(action)
|
||||
pp = pprint.PrettyPrinter(indent=1)
|
||||
for s in response:
|
||||
pp.pprint(s.__dict__)
|
||||
import pprint
|
||||
import socket
|
||||
from common import util, models
|
||||
from functools import wraps
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
from dsst_gtk3 import gtk_ui
|
||||
|
||||
|
||||
def reload_after_update(method):
|
||||
""" Method decorator to handle GUI while updating data.
|
||||
Adds error display in case of an exception and causes data reloading after an successful change.
|
||||
:param method: The method to decorate
|
||||
:return: The decorated function
|
||||
"""
|
||||
@wraps(method)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
# Set info in statusbar
|
||||
self.app.ui.get_object('status_bar').push(0, 'Connecting to server')
|
||||
try:
|
||||
method(self, *args, **kwargs)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.app.ui.get_object('status_bar').push(0, str(e))
|
||||
else:
|
||||
self.app.ui.get_object('status_bar').push(0, '')
|
||||
# Cause local data to be reloaded, if method was executed successfully
|
||||
self.app.full_reload()
|
||||
return wrapper
|
||||
|
||||
|
||||
class DataClient:
|
||||
""" The access class for reading and writing data from and to the dsst API """
|
||||
def __init__(self, app: 'gtk_ui.GtkUi', conn_dict):
|
||||
self.app = app
|
||||
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_token': self.auth_token,
|
||||
'action': action,
|
||||
'args': args}
|
||||
request = pickle.dumps(request)
|
||||
soc = socket.socket()
|
||||
try:
|
||||
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:
|
||||
soc.close()
|
||||
return message.get('data')
|
||||
|
||||
@reload_after_update
|
||||
def update_enemy(self, enemy: 'models.Enemy'):
|
||||
self.send_request('update_enemy', enemy)
|
||||
|
||||
@reload_after_update
|
||||
def update_player(self, player: 'models.Player'):
|
||||
self.send_request('update_player', player)
|
||||
|
||||
@reload_after_update
|
||||
def update_drink(self, drink: 'models.Drink'):
|
||||
self.send_request('update_drink', drink)
|
||||
|
||||
@reload_after_update
|
||||
def save_death(self, death: 'models.Death'):
|
||||
self.send_request('save_death', death)
|
||||
|
||||
@reload_after_update
|
||||
def delete_death(self, death_id: int):
|
||||
self.send_request('delete_death', death_id)
|
||||
|
||||
@reload_after_update
|
||||
def save_victory(self, victory: 'models.Victory'):
|
||||
self.send_request('save_victory', victory)
|
||||
|
||||
@reload_after_update
|
||||
def delete_victory(self, victory_id: int):
|
||||
self.send_request('delete_victory', victory_id)
|
||||
|
||||
@reload_after_update
|
||||
def update_season(self, season: 'models.Season'):
|
||||
self.send_request('update_season', season)
|
||||
|
||||
@reload_after_update
|
||||
def update_episode(self, episode: 'models.Episode'):
|
||||
self.send_request('update_episode', episode)
|
||||
|
||||
@@ -102,6 +102,13 @@ def create_death(app: 'gtk_ui.GtkUi'):
|
||||
:param app: Main Gtk application
|
||||
:return: Death object or None if dialog was canceled
|
||||
"""
|
||||
# Set penalties
|
||||
default_drink = app.drinks.data[0].name
|
||||
store = app.ui.get_object('player_penalties_store')
|
||||
store.clear()
|
||||
for player in app.ui.get_object('episode_players_store'):
|
||||
store.append([None, player[1], default_drink, player[0]])
|
||||
|
||||
# Run the dialog
|
||||
dialog = app.ui.get_object("edit_death_dialog") # type: Gtk.Dialog
|
||||
result = dialog.run()
|
||||
|
||||
@@ -6,6 +6,7 @@ from dsst_gtk3.handlers import handlers
|
||||
from dsst_gtk3 import util, reload, client
|
||||
from common import models
|
||||
|
||||
|
||||
class GtkUi:
|
||||
""" The main UI class """
|
||||
def __init__(self, config: dict):
|
||||
@@ -28,7 +29,7 @@ class GtkUi:
|
||||
self.ui.get_object('main_window').show_all()
|
||||
# Connect to data server
|
||||
config = config['servers'][0]
|
||||
self.data_client = client.Access(config)
|
||||
self.data_client = client.DataClient(self, config)
|
||||
# Create local data caches
|
||||
self.players = util.Cache()
|
||||
self.drinks = util.Cache()
|
||||
@@ -62,42 +63,6 @@ class GtkUi:
|
||||
def reload(self):
|
||||
pass
|
||||
|
||||
def update_enemy(self, enemy: 'models.Enemy'):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('update_enemy', enemy)
|
||||
self.full_reload()
|
||||
|
||||
def update_player(self, player: 'models.Player'):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('update_player', player)
|
||||
self.full_reload()
|
||||
|
||||
def update_drink(self, drink: 'models.Drink'):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('update_drink', drink)
|
||||
self.full_reload()
|
||||
|
||||
def save_death(self, death: 'models.Death'):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('save_death', death)
|
||||
self.full_reload()
|
||||
|
||||
def save_victory(self, victory: 'models.Victory'):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('save_victory', victory)
|
||||
self.full_reload()
|
||||
|
||||
def update_season(self, season: 'models.Season'):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('update_season', season)
|
||||
self.seasons.valid = False
|
||||
|
||||
def update_episode(self, episode: 'models.Episode'):
|
||||
with util.network_operation(self):
|
||||
self.data_client.send_request('update_episode', episode)
|
||||
self.episodes.valid = False
|
||||
self.season_stats.valid = False
|
||||
|
||||
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 '')
|
||||
|
||||
@@ -9,7 +9,7 @@ class BaseDataHandlers:
|
||||
|
||||
def do_add_player(self, entry):
|
||||
if entry.get_text():
|
||||
self.app.update_player(models.Player({'name': entry.get_text()}))
|
||||
self.app.data_client.update_player(models.Player({'name': entry.get_text()}))
|
||||
entry.set_text('')
|
||||
|
||||
def on_player_name_edited(self, _, index, value):
|
||||
@@ -17,29 +17,29 @@ class BaseDataHandlers:
|
||||
player = models.Player({'id': row[0],
|
||||
'name': value,
|
||||
'hex_id': row[2]})
|
||||
self.app.update_player(player)
|
||||
self.app.data_client.update_player(player)
|
||||
|
||||
def on_player_hex_edited(self, _, index, value):
|
||||
row = self.app.ui.get_object('all_players_store')[index]
|
||||
player = models.Player({'id': row[0],
|
||||
'name': row[1],
|
||||
'hex_id': value})
|
||||
self.app.update_player(player)
|
||||
self.app.data_client.update_player(player)
|
||||
|
||||
def do_add_drink(self, entry):
|
||||
if entry.get_text():
|
||||
drink = models.Drink({'name': entry.get_text(), 'vol': 0.00})
|
||||
self.app.update_drink(drink)
|
||||
self.app.data_client.update_drink(drink)
|
||||
entry.set_text('')
|
||||
|
||||
def on_drink_name_edited(self, _, index, value):
|
||||
row = self.app.ui.get_object('drink_store')[index]
|
||||
drink = [d for d in self.app.drinks.data if d.id == row[0]][0]
|
||||
drink.name = value
|
||||
self.app.update_drink(drink)
|
||||
self.app.data_client.update_drink(drink)
|
||||
|
||||
def on_drink_vol_edited(self, _, index, value):
|
||||
row = self.app.ui.get_object('drink_store')[index]
|
||||
drink = [d for d in self.app.drinks.data if d.id == row[0]][0]
|
||||
drink.vol = value
|
||||
self.app.update_drink(drink)
|
||||
self.app.data_client.update_drink(drink)
|
||||
@@ -1,5 +1,5 @@
|
||||
from gi.repository import Gtk
|
||||
from dsst_gtk3 import dialogs, gtk_ui
|
||||
from dsst_gtk3 import dialogs, gtk_ui, util
|
||||
|
||||
|
||||
class DeathHandlers:
|
||||
@@ -13,7 +13,18 @@ class DeathHandlers:
|
||||
return
|
||||
death = dialogs.create_death(self.app)
|
||||
if death:
|
||||
self.app.save_death(death)
|
||||
self.app.data_client.save_death(death)
|
||||
|
||||
def on_penalty_drink_changed(self, _, path, text):
|
||||
self.app.ui.get_object('player_penalties_store')[path][2] = text
|
||||
|
||||
def death_tree_clicked(self, widget, event):
|
||||
if event.button == 3: # right click
|
||||
popup = self.app.ui.get_object('p_death')
|
||||
util.show_context_menu(widget, event, popup)
|
||||
return True
|
||||
|
||||
def do_delete_death(self, *_):
|
||||
death_id = util.get_tree_selection_value(self.app.ui.get_object('episode_deaths_tree_view'), 0)
|
||||
self.app.data_client.delete_death(death_id)
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ class DialogHandlers:
|
||||
|
||||
def do_add_enemy(self, entry):
|
||||
if entry.get_text():
|
||||
store = self.app.ui.get_object('enemy_season_store')
|
||||
enemy = models.Enemy()
|
||||
enemy.name = entry.get_text()
|
||||
enemy.season = self.app.get_selected_season_id()
|
||||
@@ -37,20 +36,20 @@ class DialogHandlers:
|
||||
self.app.ui.get_object('enemy_optional_ckeck').set_active(False)
|
||||
entry.set_text('')
|
||||
|
||||
self.app.update_enemy(enemy)
|
||||
self.app.data_client.update_enemy(enemy)
|
||||
|
||||
def on_enemy_name_edited(self, _, index, value):
|
||||
row = self.app.ui.get_object('enemy_season_store')[index]
|
||||
enemy = [enemy for enemy in self.app.enemies.data if enemy.id == row[4]][0]
|
||||
enemy.name = value
|
||||
self.app.update_enemy(enemy)
|
||||
self.app.data_client.update_enemy(enemy)
|
||||
|
||||
def on_enemy_optional_edited(self, renderer, index):
|
||||
new_optional_value = not renderer.get_active()
|
||||
row = self.app.ui.get_object('enemy_season_store')[index]
|
||||
enemy = [enemy for enemy in self.app.enemies.data if enemy.id == row[4]][0]
|
||||
enemy.boss = new_optional_value
|
||||
self.app.update_enemy(enemy)
|
||||
self.app.data_client.update_enemy(enemy)
|
||||
|
||||
def do_show_date_picker(self, entry: 'Gtk.Entry', *_):
|
||||
dialog = self.app.ui.get_object('date_picker_dialog')
|
||||
|
||||
@@ -9,7 +9,7 @@ class SeasonHandlers:
|
||||
def do_add_season(self, *_):
|
||||
season = dialogs.edit_season(self.app.ui)
|
||||
if season:
|
||||
self.app.update_season(season)
|
||||
self.app.data_client.update_season(season)
|
||||
self.app.full_reload()
|
||||
|
||||
def do_season_selected(self, *_):
|
||||
@@ -23,7 +23,7 @@ class SeasonHandlers:
|
||||
return
|
||||
ep = dialogs.edit_episode(self.app, season_id)
|
||||
if ep:
|
||||
self.app.update_episode(ep)
|
||||
self.app.data_client.update_episode(ep)
|
||||
self.app.full_reload()
|
||||
|
||||
def on_selected_episode_changed(self, *_):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from gi.repository import Gtk
|
||||
from dsst_gtk3 import dialogs, gtk_ui
|
||||
from dsst_gtk3 import dialogs, gtk_ui, util
|
||||
|
||||
|
||||
class VictoryHandlers:
|
||||
@@ -13,4 +13,14 @@ class VictoryHandlers:
|
||||
return
|
||||
victory = dialogs.create_victory(self.app)
|
||||
if victory:
|
||||
self.app.save_victory(victory)
|
||||
self.app.data_client.save_victory(victory)
|
||||
|
||||
def victory_tree_clicked(self, widget, event):
|
||||
if event.button == 3: # right click
|
||||
popup = self.app.ui.get_object('p_victory')
|
||||
util.show_context_menu(widget, event, popup)
|
||||
return True
|
||||
|
||||
def do_delete_victory(self, *_):
|
||||
victory_id = util.get_tree_selection_value(self.app.ui.get_object('episode_victories_tree_view'), 0)
|
||||
self.app.data_client.delete_victory(victory_id)
|
||||
|
||||
@@ -82,7 +82,7 @@ def reload_episode_stats(app: 'gtk_ui.GtkUi'):
|
||||
penalties = ['{}x {}'.format(number, drink) for drink, number in Counter(penalties).items()]
|
||||
penalty_string = ', '.join(penalties)
|
||||
time_string = '{:02d}:{:02d}'.format(death.time.hour, death.time.minute)
|
||||
store.append([death.id, death.player.name, death.enemy.name, penalty_string, time_string])
|
||||
store.append([death.id, death.player.name, death.enemy.name, penalty_string, time_string, death.info])
|
||||
# Reload victory store for notebook view
|
||||
store = app.ui.get_object('episode_victories_store')
|
||||
store.clear()
|
||||
@@ -96,12 +96,22 @@ def reload_episode_stats(app: 'gtk_ui.GtkUi'):
|
||||
drink_count = sum(len(death.penalties) for death in 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)))
|
||||
|
||||
# Compute booze stats
|
||||
dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths)
|
||||
l_booze = round(dl_booze / 10, 2)
|
||||
player_ml_booze = round((dl_booze * 100) / len(episode.players), 2)
|
||||
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)
|
||||
app.ui.get_object('ep_player_booze_label').set_text('{}ml'.format(ml_booze))
|
||||
app.ui.get_object('ep_player_booze_label').set_text('{}ml'.format(player_ml_booze))
|
||||
|
||||
# Compute pure alc stats
|
||||
deaths = episode.deaths
|
||||
dl_alc = 0
|
||||
for death in deaths:
|
||||
dl_alc += sum((penalty.size / 100) * penalty.drink.vol for penalty in death.penalties)
|
||||
ml_alc = round(dl_alc * 100, 2)
|
||||
app.ui.get_object('ep_alc_label').set_text('{}ml'.format(ml_alc))
|
||||
# Compute hardest enemy stats
|
||||
enemy_list = [death.enemy.name for death in episode.deaths]
|
||||
sorted_list = Counter(enemy_list).most_common(1)
|
||||
if sorted_list:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.20.4 -->
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<object class="GtkListStore" id="all_players_store">
|
||||
@@ -53,6 +53,8 @@
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name time -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name info -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkListStore" id="episode_players_store">
|
||||
@@ -101,6 +103,31 @@
|
||||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">1</property>
|
||||
</object>
|
||||
<object class="GtkMenu" id="p_death">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="del_death">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Delete</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="do_delete_death" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkMenu" id="p_victory">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="del_victory">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Delete</property>
|
||||
<signal name="activate" handler="do_delete_victory" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkListStore" id="player_penalties_store">
|
||||
<columns>
|
||||
<!-- column-name penalty_id -->
|
||||
@@ -137,6 +164,9 @@
|
||||
<property name="default_width">1200</property>
|
||||
<property name="default_height">700</property>
|
||||
<signal name="delete-event" handler="do_delete_event" swapped="no"/>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
@@ -167,7 +197,7 @@
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem">
|
||||
<object class="GtkMenuItem" id="add_episode_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Add Episode</property>
|
||||
@@ -220,7 +250,7 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkMenuItem">
|
||||
<object class="GtkMenuItem" id="add_death_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Add Death</property>
|
||||
@@ -230,7 +260,7 @@
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem">
|
||||
<object class="GtkMenuItem" id="add_victory_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Add Victory</property>
|
||||
@@ -263,7 +293,7 @@
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem">
|
||||
<object class="GtkMenuItem" id="manage_drinks_item">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Manage Drinks</property>
|
||||
@@ -542,7 +572,7 @@
|
||||
<object class="GtkLabel" id="ep_death_count_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">[DeathCount]</property>
|
||||
<property name="label" translatable="yes">Computing...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
@@ -553,51 +583,7 @@
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Most Deaths:</property>
|
||||
<property name="xalign">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Hardest Enemy:</property>
|
||||
<property name="xalign">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ep_enemy_name_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ep_worst_player_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Total Drinks:</property>
|
||||
<property name="label" translatable="yes">Total Shots:</property>
|
||||
<property name="xalign">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
@@ -609,7 +595,7 @@
|
||||
<object class="GtkLabel" id="ep_drinks_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">[DrinkCount]</property>
|
||||
<property name="label" translatable="yes">Computing...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
@@ -632,7 +618,7 @@
|
||||
<object class="GtkLabel" id="ep_booze_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">[ToatalBooze]</property>
|
||||
<property name="label" translatable="yes">Computing...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
@@ -691,7 +677,7 @@
|
||||
<object class="GtkLabel" id="ep_player_drinks_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">[PlayerDrinks]</property>
|
||||
<property name="label" translatable="yes">Computing...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
@@ -702,7 +688,7 @@
|
||||
<object class="GtkLabel" id="ep_player_booze_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">[PlayerBooze]</property>
|
||||
<property name="label" translatable="yes">Computing...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
@@ -713,7 +699,7 @@
|
||||
<object class="GtkLabel" id="ep_player_alc_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">[PlayerAlc]</property>
|
||||
<property name="label" translatable="yes">Computing...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
@@ -724,13 +710,36 @@
|
||||
<object class="GtkLabel" id="ep_alc_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">[TotalAlc]</property>
|
||||
<property name="label" translatable="yes">Computing...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Hardest Enemy:</property>
|
||||
<property name="xalign">1</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="ep_enemy_name_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Computing...</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="top_attach">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@@ -791,6 +800,7 @@
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">episode_deaths_store</property>
|
||||
<property name="search_column">0</property>
|
||||
<signal name="button-press-event" handler="death_tree_clicked" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
@@ -838,6 +848,17 @@
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="title" translatable="yes">Comment</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -871,6 +892,7 @@
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">episode_victories_store</property>
|
||||
<signal name="button-press-event" handler="victory_tree_clicked" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
@@ -1309,9 +1331,6 @@
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="titlebar">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="date_picker_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -1321,6 +1340,9 @@
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="deletable">False</property>
|
||||
<property name="transient_for">main_window</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -1423,9 +1445,6 @@
|
||||
<action-widget response="-5">button3</action-widget>
|
||||
<action-widget response="-6">button6</action-widget>
|
||||
</action-widgets>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="edit_episode_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -1435,6 +1454,9 @@
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="deletable">False</property>
|
||||
<property name="transient_for">main_window</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -1731,9 +1753,6 @@
|
||||
<action-widget response="-5">button1</action-widget>
|
||||
<action-widget response="-6">button2</action-widget>
|
||||
</action-widgets>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="edit_season_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -1743,6 +1762,9 @@
|
||||
<property name="deletable">False</property>
|
||||
<property name="transient_for">main_window</property>
|
||||
<property name="attached_to">main_window</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -1952,9 +1974,6 @@
|
||||
<action-widget response="-5">button4</action-widget>
|
||||
<action-widget response="-6">button5</action-widget>
|
||||
</action-widgets>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="edit_victory_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -1964,6 +1983,9 @@
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="deletable">False</property>
|
||||
<property name="transient_for">main_window</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -2235,9 +2257,6 @@
|
||||
<action-widget response="-5">okButtonRename5</action-widget>
|
||||
<action-widget response="-6">cancelButtonRename1</action-widget>
|
||||
</action-widgets>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="manage_drinks_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -2248,6 +2267,9 @@
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="deletable">False</property>
|
||||
<property name="transient_for">main_window</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -2405,9 +2427,6 @@
|
||||
<action-widgets>
|
||||
<action-widget response="-5">okButtonRename3</action-widget>
|
||||
</action-widgets>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="manage_enemies_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -2418,6 +2437,9 @@
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="deletable">False</property>
|
||||
<property name="transient_for">main_window</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -2588,9 +2610,6 @@
|
||||
<action-widgets>
|
||||
<action-widget response="-5">okButtonRename1</action-widget>
|
||||
</action-widgets>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkDialog" id="manage_players_dialog">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -2601,6 +2620,9 @@
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="deletable">False</property>
|
||||
<property name="transient_for">main_window</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -2757,9 +2779,6 @@
|
||||
<action-widgets>
|
||||
<action-widget response="-5">okButtonRename2</action-widget>
|
||||
</action-widgets>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="shot_size_adjustment">
|
||||
<property name="upper">100</property>
|
||||
@@ -2774,6 +2793,9 @@
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="deletable">False</property>
|
||||
<property name="transient_for">main_window</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can_focus">False</property>
|
||||
@@ -3135,8 +3157,5 @@
|
||||
<action-widget response="-5">okButtonRename4</action-widget>
|
||||
<action-widget response="-6">cancelButtonRename4</action-widget>
|
||||
</action-widgets>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
@@ -14,7 +14,7 @@ DEFAULT_CONFIG = {
|
||||
'auto_connect': False,
|
||||
'servers': [{
|
||||
'host': 'localhost',
|
||||
'port': 12345,
|
||||
'port': 55225,
|
||||
'buffer_size': 1024,
|
||||
'auth_token': ''}
|
||||
]
|
||||
@@ -75,6 +75,17 @@ def get_combo_value(combo, index: int):
|
||||
return -1
|
||||
|
||||
|
||||
def get_tree_selection_value(tree: 'Gtk.TreeView', column: int):
|
||||
""" Retrieve the a cell value of a tree view based on selected row and the choosen column
|
||||
:param tree: Gtk.TreeView widget
|
||||
:param column: Number of the column from which to retrieve the value
|
||||
"""
|
||||
(model, pathlist) = tree.get_selection().get_selected_rows()
|
||||
for path in pathlist:
|
||||
tree_iter = model.get_iter(path)
|
||||
return model.get_value(tree_iter, 0)
|
||||
|
||||
|
||||
def get_index_of_combo_model(widget, column: int, value: int):
|
||||
"""Get the index of a value within a Gtk widgets model based on column an value
|
||||
:param widget: Any Gtk widget that can be bound to a ListStore or TreeStore
|
||||
@@ -147,3 +158,17 @@ def save_config(config: dict, config_path: str):
|
||||
os.mkdir(path)
|
||||
with open(config_path, 'wb') as file:
|
||||
file.write(json.dumps(config, sort_keys=True, indent=4, separators=(',', ': ')).encode('utf-8'))
|
||||
|
||||
|
||||
def show_context_menu(tree: 'Gtk.TreeView', event, popup: 'Gtk.Menu'):
|
||||
path = tree.get_path_at_pos(int(event.x), int(event.y))
|
||||
# Get the selection
|
||||
selection = tree.get_selection()
|
||||
# Get the selected path(s)
|
||||
rows = selection.get_selected_rows()
|
||||
# If not clicked on selection, change selected rows
|
||||
if path:
|
||||
if path[0] not in rows[1]:
|
||||
selection.unselect_all()
|
||||
selection.select_path(path[0])
|
||||
popup.popup(None, None, None, None, 0, event.time)
|
||||
|
||||
33
dsst/dsst_server/auth.py
Normal file
33
dsst/dsst_server/auth.py
Normal file
@@ -0,0 +1,33 @@
|
||||
READ_TOKENS = []
|
||||
WRITE_TOKENS = []
|
||||
|
||||
|
||||
class AuthenticationError(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def get_response(self):
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'Authentication Failed:\n'.format(self.message)
|
||||
}
|
||||
|
||||
|
||||
def check_read(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
token = args[0]
|
||||
if token in READ_TOKENS + WRITE_TOKENS:
|
||||
return func(*args[1:], **kwargs)
|
||||
else:
|
||||
raise AuthenticationError('Token "{}" has no read access on database.'.format(token))
|
||||
return wrapper
|
||||
|
||||
|
||||
def check_write(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
token = args[0]
|
||||
if token in WRITE_TOKENS:
|
||||
return func(*args[1:], **kwargs)
|
||||
else:
|
||||
raise AuthenticationError('Token "{}" has no write access on database.'.format(token))
|
||||
return wrapper
|
||||
45
dsst/dsst_server/config.py
Normal file
45
dsst/dsst_server/config.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# This config will be copied on first launch.
|
||||
# Edit the the copied file at '~/.config/dsst/server.json' before launching the server
|
||||
# IMPORTANT: For a client to access the server you have to set authentication tokens in the server config
|
||||
DEFAULT_CONFIG = {
|
||||
'database': {
|
||||
'db_name': 'dsst',
|
||||
'user': 'dsst',
|
||||
'password': 'dsst'
|
||||
},
|
||||
'server': {
|
||||
'port': 55225,
|
||||
'buffer_size': 1024
|
||||
},
|
||||
'tokens': {
|
||||
'readonly': [],
|
||||
'readwrite': []
|
||||
},
|
||||
'logging': dict(
|
||||
version=1,
|
||||
disable_existing_loggers=False,
|
||||
formatters={
|
||||
'file': {'format': '[%(levelname)s] %(asctime)s - %(message)s',
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S'},
|
||||
'std': {'format': '[%(levelname)s] %(message)s'}
|
||||
},
|
||||
handlers={
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'std',
|
||||
'level': 'INFO'
|
||||
},
|
||||
'logfile': {
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'maxBytes': 3145728,
|
||||
'backupCount': 1,
|
||||
'formatter': 'file',
|
||||
'level': 'INFO'
|
||||
}
|
||||
|
||||
},
|
||||
root={
|
||||
'handlers': ['console', 'logfile'],
|
||||
'level': 'INFO'
|
||||
})
|
||||
}
|
||||
@@ -12,7 +12,7 @@ try:
|
||||
from peewee import *
|
||||
except ImportError:
|
||||
print('peewee package not installed')
|
||||
sys.exit(0)
|
||||
sys.exit(1)
|
||||
|
||||
db = MySQLDatabase(None)
|
||||
|
||||
@@ -43,7 +43,7 @@ class Episode(Model):
|
||||
number = CharField()
|
||||
name = CharField(null=True)
|
||||
date = DateField(null=True)
|
||||
season = ForeignKeyField(Season, backref='episodes')
|
||||
season = ForeignKeyField(Season, backref='episodes', on_delete='CASCADE')
|
||||
players = ManyToManyField(Player, backref='episodes')
|
||||
|
||||
class Meta:
|
||||
@@ -63,7 +63,7 @@ class Enemy(Model):
|
||||
id = AutoField()
|
||||
name = CharField()
|
||||
boss = BooleanField()
|
||||
season = ForeignKeyField(Season, backref='enemies')
|
||||
season = ForeignKeyField(Season, backref='enemies', on_delete='CASCADE')
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
@@ -75,7 +75,7 @@ class Death(Model):
|
||||
time = TimeField(default=datetime.time(0, 0))
|
||||
player = ForeignKeyField(Player)
|
||||
enemy = ForeignKeyField(Enemy)
|
||||
episode = ForeignKeyField(Episode, backref='deaths')
|
||||
episode = ForeignKeyField(Episode, backref='deaths', on_delete='CASCADE')
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
@@ -85,8 +85,8 @@ class Penalty(Model):
|
||||
id = AutoField()
|
||||
size = DecimalField()
|
||||
drink = ForeignKeyField(Drink)
|
||||
player = ForeignKeyField(Player, backref='penalties')
|
||||
death = ForeignKeyField(Death, backref='penalties')
|
||||
player = ForeignKeyField(Player, backref='penalties', on_delete='CASCADE')
|
||||
death = ForeignKeyField(Death, backref='penalties', on_delete='CASCADE')
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
@@ -98,7 +98,7 @@ class Victory(Model):
|
||||
time = TimeField(default=datetime.time(0, 0))
|
||||
player = ForeignKeyField(Player)
|
||||
enemy = ForeignKeyField(Enemy)
|
||||
episode = ForeignKeyField(Episode, backref='victories')
|
||||
episode = ForeignKeyField(Episode, backref='victories', on_delete='CASCADE')
|
||||
|
||||
class Meta:
|
||||
database = db
|
||||
|
||||
15
dsst/dsst_server/func_delete.py
Normal file
15
dsst/dsst_server/func_delete.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from dsst_server.data_access import sql
|
||||
from dsst_server.auth import check_write
|
||||
|
||||
|
||||
class DeleteFunctions:
|
||||
|
||||
@staticmethod
|
||||
@check_write
|
||||
def delete_death(death_id: int):
|
||||
return sql.Death.delete().where(sql.Death.id == death_id).execute()
|
||||
|
||||
@staticmethod
|
||||
@check_write
|
||||
def delete_victory(victory_id: int):
|
||||
return sql.Victory.delete().where(sql.Death.id == victory_id).execute()
|
||||
@@ -1,6 +1,7 @@
|
||||
from dsst_server.func_write import WriteFunctions
|
||||
from dsst_server.func_read import ReadFunctions
|
||||
from dsst_server.func_delete import DeleteFunctions
|
||||
|
||||
|
||||
class FunctionProxy(WriteFunctions, ReadFunctions):
|
||||
class FunctionProxy(WriteFunctions, ReadFunctions, DeleteFunctions):
|
||||
pass
|
||||
|
||||
@@ -1,40 +1,49 @@
|
||||
from dsst_server.data_access import sql, sql_func, mapping
|
||||
from dsst_server.auth import check_read
|
||||
from common import models
|
||||
from playhouse import shortcuts
|
||||
|
||||
|
||||
class ReadFunctions:
|
||||
@staticmethod
|
||||
@check_read
|
||||
def load_db_meta(*_):
|
||||
return sql.db.database
|
||||
|
||||
@staticmethod
|
||||
@check_read
|
||||
def load_seasons(*_):
|
||||
return [mapping.db_to_season(season) for season in sql.Season.select()]
|
||||
|
||||
@staticmethod
|
||||
@check_read
|
||||
def load_seasons_all(*_):
|
||||
return [shortcuts.model_to_dict(season, backrefs=True, max_depth=2) for season in sql.Season.select()]
|
||||
|
||||
@staticmethod
|
||||
@check_read
|
||||
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
|
||||
@check_read
|
||||
def load_players(*_):
|
||||
return [mapping.db_to_player(player) for player in sql.Player.select()]
|
||||
|
||||
@staticmethod
|
||||
@check_read
|
||||
def load_enemies(season_id, *_):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@check_read
|
||||
def load_drinks(*_):
|
||||
return [mapping.db_to_drink(drink) for drink in sql.Drink.select()]
|
||||
|
||||
@staticmethod
|
||||
@check_read
|
||||
def load_season_stats(season_id, *_):
|
||||
season = sql.Season.get(sql.Season.id == season_id)
|
||||
players = sql_func.players_for_season(season_id)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
from common import models
|
||||
from dsst_server.data_access import sql
|
||||
from dsst_server.auth import check_write
|
||||
|
||||
|
||||
class WriteFunctions:
|
||||
@staticmethod
|
||||
@check_write
|
||||
def create_season(season: 'models.Season'):
|
||||
return 'Season created.'
|
||||
|
||||
@staticmethod
|
||||
@check_write
|
||||
def update_enemy(enemy: 'models.Enemy', *_):
|
||||
(sql.Enemy
|
||||
.insert(id=enemy.id, boss=enemy.boss, name=enemy.name, season=enemy.season)
|
||||
@@ -17,6 +20,7 @@ class WriteFunctions:
|
||||
.execute())
|
||||
|
||||
@staticmethod
|
||||
@check_write
|
||||
def update_player(player: 'models.Player', *_):
|
||||
(sql.Player
|
||||
.insert(id=player.id, name=player.name, hex_id=player.hex_id)
|
||||
@@ -25,6 +29,7 @@ class WriteFunctions:
|
||||
.execute())
|
||||
|
||||
@staticmethod
|
||||
@check_write
|
||||
def update_drink(drink: 'models.Drink', *_):
|
||||
(sql.Drink
|
||||
.insert(id=drink.id, name=drink.name, vol=drink.vol)
|
||||
@@ -33,6 +38,7 @@ class WriteFunctions:
|
||||
.execute())
|
||||
|
||||
@staticmethod
|
||||
@check_write
|
||||
def save_death(death: 'models.Death'):
|
||||
with sql.db.atomic():
|
||||
created_id = (sql.Death
|
||||
@@ -43,6 +49,7 @@ class WriteFunctions:
|
||||
sql.Penalty.create(death=created_id, size=penalty.size, drink=penalty.drink, player=penalty.player)
|
||||
|
||||
@staticmethod
|
||||
@check_write
|
||||
def save_victory(victory: 'models.Victory'):
|
||||
(sql.Victory
|
||||
.insert(info=victory.info, player=victory.player, enemy=victory.enemy, time=victory.time,
|
||||
@@ -50,6 +57,7 @@ class WriteFunctions:
|
||||
.execute())
|
||||
|
||||
@staticmethod
|
||||
@check_write
|
||||
def update_season(season: 'models.Season', *_):
|
||||
(sql.Season
|
||||
.insert(id=season.id, number=season.number, game_name=season.game_name, start_date=season.start_date,
|
||||
@@ -62,6 +70,7 @@ class WriteFunctions:
|
||||
.execute())
|
||||
|
||||
@staticmethod
|
||||
@check_write
|
||||
def update_episode(episode: 'models.Episode', *_):
|
||||
players = list(sql.Player.select().where(sql.Player.id << [player.id for player in episode.players]))
|
||||
new_ep_id = (sql.Episode
|
||||
@@ -76,3 +85,8 @@ class WriteFunctions:
|
||||
db_episode = sql.Episode.get(sql.Episode.id == new_ep_id)
|
||||
db_episode.players = players
|
||||
db_episode.save()
|
||||
|
||||
@staticmethod
|
||||
@check_write
|
||||
def delete_player(player_id: int, *_):
|
||||
sql.Player.delete_by_id(int)
|
||||
|
||||
@@ -1,83 +1,102 @@
|
||||
import json
|
||||
import pickle
|
||||
import socket
|
||||
|
||||
import sys
|
||||
|
||||
import os
|
||||
import logging
|
||||
from logging.config import dictConfig
|
||||
|
||||
from common import util, models
|
||||
from dsst_server import func_read, func_write, tokens
|
||||
from common import util
|
||||
from dsst_server import auth
|
||||
from dsst_server.func_proxy import FunctionProxy
|
||||
from dsst_server.data_access import sql, sql_func
|
||||
|
||||
PORT = 12345
|
||||
HOST = socket.gethostname()
|
||||
BUFFER_SIZE = 1024
|
||||
from dsst_server.config import DEFAULT_CONFIG
|
||||
|
||||
|
||||
class DsstServer:
|
||||
def __init__(self, config={}):
|
||||
def __init__(self, config):
|
||||
# Initialize the logger
|
||||
try:
|
||||
logger_dict = config.get('logging')
|
||||
logfile = os.path.join(os.path.expanduser("~"), '.config', 'dsst', 'server.log')
|
||||
logger_dict.get('handlers').get('logfile')['filename'] = logfile
|
||||
dictConfig(logger_dict)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
|
||||
# Create ands bind the socket server
|
||||
self.socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
print('Created socket')
|
||||
logging.info('Created socket')
|
||||
server_conf = config.get('server')
|
||||
try:
|
||||
self.socket_server.bind(('', server_conf.get('port')))
|
||||
logging.info('Bound socket to port {}'.format(server_conf.get('port')))
|
||||
except OSError as e:
|
||||
logging.error('Cannot bind socket: {})'.format(e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
self.socket_server.bind((HOST, PORT))
|
||||
print('Bound socket to {} on host {}'.format(PORT, HOST))
|
||||
|
||||
# Initialize database
|
||||
# Save Database credentials
|
||||
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))
|
||||
self.db_user = db_config.get('user')
|
||||
self.db_name = db_config.get('db_name')
|
||||
self.db_password = db_config.get('password')
|
||||
|
||||
# Load access tokens and map them to their allowed methods
|
||||
read_actions = util.list_class_methods(func_read.ReadFunctions)
|
||||
write_actions = util.list_class_methods(func_write.WriteFunctions)
|
||||
parm_access = {
|
||||
'r': read_actions,
|
||||
'rw': read_actions + write_actions
|
||||
}
|
||||
self.tokens = {token: parm_access[perms] for token, perms in tokens.TOKENS}
|
||||
print('Loaded auth tokens: {}'.format(self.tokens.keys()))
|
||||
# Load access tokens
|
||||
auth.READ_TOKENS = config.get('tokens').get('readonly')
|
||||
auth.WRITE_TOKENS = config.get('tokens').get('readwrite')
|
||||
logging.info('Auth tokens loaded')
|
||||
|
||||
def process_request(self, request: dict) -> dict:
|
||||
""" Process a requested function from a client
|
||||
:param request: Request dictionary
|
||||
:return: Response dictionary
|
||||
"""
|
||||
# Get requested function from the function proxy
|
||||
action_name = request.get('action')
|
||||
action = getattr(FunctionProxy, action_name)
|
||||
try:
|
||||
# Open a database connection
|
||||
sql.db.init(self.db_name, user=self.db_user, password=self.db_password)
|
||||
logging.info('Connected to database ({})'.format(self.db_name))
|
||||
# Execute the function
|
||||
result = action(request.get('auth_token'), *request.get('args'))
|
||||
logging.info('Operation executed successfully')
|
||||
return {'success': True, 'data': result}
|
||||
except auth.AuthenticationError as e:
|
||||
logging.error(e.get_response())
|
||||
return e.get_response()
|
||||
except Exception as e:
|
||||
logging.error('Exception was thrown: ' + str(e))
|
||||
return {'success': False, 'message': 'Exception was thrown on server.'}
|
||||
finally:
|
||||
sql.db.close()
|
||||
logging.info('Database connection closed')
|
||||
|
||||
def run(self):
|
||||
self.socket_server.listen(5)
|
||||
print('Socket is listening')
|
||||
logging.info('Socket is listening')
|
||||
|
||||
while True:
|
||||
# Accept client connection
|
||||
client, address = self.socket_server.accept()
|
||||
try:
|
||||
print('Connection from {}'.format(address))
|
||||
data = util.recv_msg(client)
|
||||
request = pickle.loads(data)
|
||||
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('Rejected request from {}. Auth token invalid ({})'.format(address, token))
|
||||
continue
|
||||
# Check read functions
|
||||
action_name = request.get('action')
|
||||
if action_name in self.tokens[token]:
|
||||
action = getattr(FunctionProxy, action_name)
|
||||
try:
|
||||
value = action(*request.get('args'))
|
||||
except Exception as 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 = '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)
|
||||
finally:
|
||||
client.close()
|
||||
print('Connection to client closed')
|
||||
logging.info('-' * 30)
|
||||
logging.info('Connection from {}'.format(address))
|
||||
|
||||
# Parse request from client
|
||||
data = util.recv_msg(client)
|
||||
request = pickle.loads(data)
|
||||
logging.info('Request: {}'.format(request))
|
||||
|
||||
# Process request
|
||||
response = self.process_request(request)
|
||||
|
||||
# Send data back to client
|
||||
util.send_msg(client, pickle.dumps(response))
|
||||
|
||||
# Close connection
|
||||
client.close()
|
||||
logging.info('Connection to client closed')
|
||||
|
||||
|
||||
def load_config(config_path: str) -> dict:
|
||||
@@ -85,13 +104,28 @@ def load_config(config_path: str) -> dict:
|
||||
return json.load(config_file)
|
||||
|
||||
|
||||
def save_config(config: dict, config_path: str):
|
||||
path = os.path.dirname(config_path)
|
||||
if not os.path.isdir(path):
|
||||
os.mkdir(path)
|
||||
with open(config_path, 'wb') as file:
|
||||
file.write(json.dumps(config, sort_keys=True, indent=4, separators=(',', ': ')).encode('utf-8'))
|
||||
|
||||
|
||||
def main():
|
||||
config = os.path.join(os.path.expanduser('~'), '.config', 'dsst', 'server.json')
|
||||
if not os.path.isfile(config):
|
||||
save_config(DEFAULT_CONFIG, config)
|
||||
logging.error('No server config file found.\n'
|
||||
'Copied default config to "{}"\n'
|
||||
'Please edit file before starting server.'
|
||||
.format(config))
|
||||
sys.exit(0)
|
||||
server = DsstServer(load_config(config))
|
||||
try:
|
||||
server.run()
|
||||
except KeyboardInterrupt:
|
||||
print('Server stopped')
|
||||
logging.info('Server stopped')
|
||||
server.socket_server.close()
|
||||
try:
|
||||
sys.exit(0)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Define access tokens here
|
||||
# i.E: { 'read': ['a', 'b''],
|
||||
# 'write': ['a']
|
||||
# }
|
||||
TOKENS = [('a', 'rw'), ('b', 'r')]
|
||||
Reference in New Issue
Block a user