Merge pull request #1 from luxick/client_server
Merge Client/Server Branch
This commit is contained in:
30
README.md
30
README.md
@@ -2,19 +2,33 @@
|
||||
Over-engineered statistics tool for keeping track of a drinking game
|
||||
|
||||
## Running the application
|
||||
### Build executable zip archive
|
||||
Run build script
|
||||
The application is split into an server and client part.
|
||||
To run standalone on a single machine you will have to have a running
|
||||
dsst-server that is connected to a mysql database.
|
||||
|
||||
`$ python3 ./build.py`
|
||||
Using the client you can then connect to the server via its
|
||||
specified port.
|
||||
### Building
|
||||
Run the build script with the desired option
|
||||
|
||||
The archive will be saved into the `build` folder. The file is completly standalone and can be run from anywhere.
|
||||
`$ python3 ./build.py {gtk3|server|all}`
|
||||
|
||||
`$ ./build/dsst`
|
||||
The archive(s) will be saved into the `build` folder.
|
||||
To run either server or client, just execute the archive files.
|
||||
|
||||
### Run python script directly
|
||||
`$ python3 ./dsst/__main__.py`
|
||||
`$ ./build/dsst-server-0.1`
|
||||
|
||||
To run the server.
|
||||
|
||||
`$ ./build/dsst-gtk3-0.1`
|
||||
|
||||
To run the GTK client.
|
||||
|
||||
## Dependencies
|
||||
- GObject (Gtk3)
|
||||
- Python 3
|
||||
### Client
|
||||
- python-gi <= v3.16 (Gtk3)
|
||||
|
||||
### Server
|
||||
- mysqlclient (Python Mysql Driver)
|
||||
- peewee (ORM Framework)
|
||||
|
||||
62
build.py
62
build.py
@@ -3,16 +3,60 @@ Package application using zipapp into an executable zip archive
|
||||
"""
|
||||
import os
|
||||
import zipapp
|
||||
import sys
|
||||
|
||||
import shutil
|
||||
|
||||
INTERPRETER = '/usr/bin/env python3'
|
||||
SOURCE_PATH = 'dsst'
|
||||
TARGET_FILENAME = 'dsst'
|
||||
|
||||
# The bundled file should be placed into the build directory
|
||||
target_path = os.path.join(os.path.dirname(__file__), 'build')
|
||||
CLIENT_VERSION = '0.1'
|
||||
SERVER_VERSION = '0.1'
|
||||
|
||||
try:
|
||||
build_mode = sys.argv[1]
|
||||
except IndexError:
|
||||
print('No build mode specified')
|
||||
sys.exit(0)
|
||||
|
||||
print('Building Mode: {}'.format(build_mode))
|
||||
|
||||
path = os.path.dirname(__file__)
|
||||
# Specify build path
|
||||
BUILD_PATH = os.path.join(path, 'build')
|
||||
# Make sure it exists
|
||||
if not os.path.isdir(target_path):
|
||||
os.mkdir(target_path)
|
||||
target = os.path.join(target_path, TARGET_FILENAME)
|
||||
# Create archive
|
||||
zipapp.create_archive(source=SOURCE_PATH, target=target, interpreter=INTERPRETER)
|
||||
if not os.path.isdir(BUILD_PATH):
|
||||
os.mkdir(BUILD_PATH)
|
||||
|
||||
|
||||
def build(target_filename, folder_name, entry_point):
|
||||
source_path = os.path.join(BUILD_PATH, 'source')
|
||||
if os.path.isdir(source_path):
|
||||
shutil.rmtree(source_path)
|
||||
os.mkdir(source_path)
|
||||
shutil.copytree(os.path.join(path, 'dsst', folder_name), os.path.join(source_path, folder_name))
|
||||
shutil.copytree(os.path.join(path, 'dsst', 'common'), os.path.join(source_path, 'common'))
|
||||
archive_name = os.path.join(BUILD_PATH, target_filename)
|
||||
zipapp.create_archive(source=source_path, target=archive_name, interpreter=INTERPRETER,
|
||||
main=entry_point)
|
||||
print('Created {}'.format(archive_name))
|
||||
shutil.rmtree(source_path)
|
||||
|
||||
|
||||
def build_server():
|
||||
build('dsst-server-{}'.format(SERVER_VERSION), 'dsst_server', 'dsst_server.server:main')
|
||||
|
||||
|
||||
def build_gtk3():
|
||||
build('dsst-gtk3-{}'.format(CLIENT_VERSION), 'dsst_gtk3', 'dsst_gtk3.gtk_ui:main')
|
||||
|
||||
|
||||
build_modes = {
|
||||
'server': build_server,
|
||||
'gtk3': build_gtk3
|
||||
}
|
||||
|
||||
if build_mode == 'all':
|
||||
for mode, build_function in build_modes.items():
|
||||
build_function()
|
||||
else:
|
||||
build_modes[build_mode]()
|
||||
85
dsst/common/models.py
Normal file
85
dsst/common/models.py
Normal file
@@ -0,0 +1,85 @@
|
||||
class Season:
|
||||
def __init__(self, arg={}):
|
||||
self.id = arg.get('id')
|
||||
self.number = arg.get('number')
|
||||
self.game_name = arg.get('game_name')
|
||||
self.start_date = arg.get('start_date')
|
||||
self.end_date = arg.get('end_date')
|
||||
|
||||
self.episodes = arg.get('episodes')
|
||||
self.enemies = arg.get('enemies')
|
||||
|
||||
|
||||
class Player:
|
||||
def __init__(self, arg={}):
|
||||
self.id = arg.get('id')
|
||||
self.name = arg.get('name')
|
||||
self.hex_id = arg.get('hex_id')
|
||||
|
||||
self.deaths = arg.get('deaths')
|
||||
self.victories = arg.get('victories')
|
||||
self.penalties = arg.get('penalties')
|
||||
|
||||
|
||||
class Episode:
|
||||
def __init__(self, arg={}):
|
||||
self.id = arg.get('id')
|
||||
self.seq_number = arg.get('seq_number')
|
||||
self.number = arg.get('number')
|
||||
self.name = arg.get('name')
|
||||
self.date = arg.get('date')
|
||||
self.season = arg.get('season')
|
||||
self.players = arg.get('players')
|
||||
self.deaths = arg.get('deaths')
|
||||
self.victories = arg.get('victories')
|
||||
|
||||
|
||||
class Drink:
|
||||
def __init__(self, arg={}):
|
||||
self.id = arg.get('id')
|
||||
self.name = arg.get('name')
|
||||
self.vol = arg.get('vol')
|
||||
|
||||
|
||||
class Enemy:
|
||||
def __init__(self, arg={}):
|
||||
self.id = arg.get('id')
|
||||
self.name = arg.get('name')
|
||||
self.boss = arg.get('boss')
|
||||
self.season = arg.get('season')
|
||||
|
||||
|
||||
class Death:
|
||||
def __init__(self, arg={}):
|
||||
self.id = arg.get('id')
|
||||
self.info = arg.get('info')
|
||||
self.player = arg.get('player')
|
||||
self.enemy = arg.get('enemy')
|
||||
self.episode = arg.get('episode')
|
||||
self.penalties = arg.get('penalties')
|
||||
self.time = arg.get('time')
|
||||
|
||||
|
||||
class Penalty:
|
||||
def __init__(self, arg={}):
|
||||
self.id = arg.get('id')
|
||||
self.size = arg.get('size')
|
||||
self.drink = arg.get('drink')
|
||||
self.player = arg.get('player')
|
||||
self.death = arg.get('death')
|
||||
|
||||
|
||||
class Victory:
|
||||
def __init__(self, arg={}):
|
||||
self.id = arg.get('id')
|
||||
self.info = arg.get('info')
|
||||
self.player = arg.get('player')
|
||||
self.enemy = arg.get('enemy')
|
||||
self.episode = arg.get('episode')
|
||||
self.time = arg.get('time')
|
||||
|
||||
|
||||
class SeasonStats:
|
||||
def __init__(self, arg={}):
|
||||
self.player_kd = arg.get('player_kd')
|
||||
self.enemies = arg.get('enemies')
|
||||
32
dsst/common/util.py
Normal file
32
dsst/common/util.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import struct
|
||||
|
||||
|
||||
def send_msg(sock, msg):
|
||||
# Prefix each message with a 4-byte length (network byte order)
|
||||
msg = struct.pack('>I', len(msg)) + msg
|
||||
sock.sendall(msg)
|
||||
|
||||
|
||||
def recv_msg(sock):
|
||||
# Read message length and unpack it into an integer
|
||||
raw_msglen = recvall(sock, 4)
|
||||
if not raw_msglen:
|
||||
return None
|
||||
msglen = struct.unpack('>I', raw_msglen)[0]
|
||||
# Read the message data
|
||||
return recvall(sock, msglen)
|
||||
|
||||
|
||||
def recvall(sock, n):
|
||||
# Helper function to recv n bytes or return None if EOF is hit
|
||||
data = b''
|
||||
while len(data) < n:
|
||||
packet = sock.recv(n - len(data))
|
||||
if not packet:
|
||||
return None
|
||||
data += packet
|
||||
return data
|
||||
|
||||
|
||||
def list_class_methods(class_obj):
|
||||
return [name for name in dir(class_obj) if not name.startswith('__')]
|
||||
@@ -1,8 +1,9 @@
|
||||
import sys
|
||||
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(path))
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(path)))
|
||||
|
||||
from dsst_gtk3 import gtk_ui
|
||||
|
||||
40
dsst/dsst_gtk3/client.py
Normal file
40
dsst/dsst_gtk3/client.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import pprint
|
||||
import socket
|
||||
from common import util, models
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
|
||||
|
||||
class Access:
|
||||
def __init__(self, conn_dict):
|
||||
self.host = conn_dict.get('host')
|
||||
self.port = conn_dict.get('port')
|
||||
self.buffer = conn_dict.get('buffer_size')
|
||||
self.auth_token = conn_dict.get('auth_token')
|
||||
|
||||
def send_request(self, action: str, *args):
|
||||
request = {'auth_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')
|
||||
|
||||
if __name__ == '__main__':
|
||||
access = Access({'host': 'europa', 'port': 12345, 'buffer_size': 1024, 'auth_token': 'a'})
|
||||
action = 'load_seasons'
|
||||
response = access.send_request(action)
|
||||
pp = pprint.PrettyPrinter(indent=1)
|
||||
for s in response:
|
||||
pp.pprint(s.__dict__)
|
||||
@@ -1,12 +1,10 @@
|
||||
"""
|
||||
This module contains UI functions for displaying different dialogs
|
||||
"""
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
import datetime
|
||||
from gi.repository import Gtk
|
||||
from datetime import datetime
|
||||
from dsst_sql import sql
|
||||
from dsst_gtk3 import util
|
||||
from common import models
|
||||
from dsst_gtk3 import gtk_ui, util
|
||||
|
||||
|
||||
def enter_string_dialog(builder: Gtk.Builder, title: str, value=None) -> str:
|
||||
@@ -33,159 +31,122 @@ def enter_string_dialog(builder: Gtk.Builder, title: str, value=None) -> str:
|
||||
return value
|
||||
|
||||
|
||||
def show_episode_dialog(builder: Gtk.Builder, title: str, season_id: int, episode: sql.Episode=None):
|
||||
""" Shows a dialog to edit an episode
|
||||
:param builder: GtkBuilder with loaded 'dialogs.glade'
|
||||
:param title: Title of the dialog window
|
||||
:param season_id: Season to witch the episode should be added
|
||||
:param episode: (Optional) Existing episode to edit
|
||||
:return True if changes where saved False if discarded
|
||||
def edit_season(builder: 'Gtk.Builder', season: 'models.Season'=None):
|
||||
if not season:
|
||||
season = models.Season()
|
||||
builder.get_object('season_number_spin').set_value(season.number or 1)
|
||||
builder.get_object('season_game_entry').set_text(season.game_name or '')
|
||||
builder.get_object('season_start_entry').set_text(season.start_date or '')
|
||||
builder.get_object('season_end_entry').set_text(season.end_date or '')
|
||||
|
||||
dialog = builder.get_object('edit_season_dialog')
|
||||
result = dialog.run()
|
||||
dialog.hide()
|
||||
|
||||
if result != Gtk.ResponseType.OK:
|
||||
return None
|
||||
|
||||
season.number = builder.get_object('season_number_spin').get_value()
|
||||
season.game_name = builder.get_object('season_game_entry').get_text()
|
||||
start_string = builder.get_object('season_start_entry').get_text()
|
||||
if start_string:
|
||||
season.start_date = datetime.datetime.strptime(start_string, '%Y-%m-%d')
|
||||
end_string = builder.get_object('season_end_entry').get_text()
|
||||
if end_string:
|
||||
season.end_date = datetime.datetime.strptime(end_string, '%Y-%m-%d')
|
||||
return season
|
||||
|
||||
|
||||
def edit_episode(app: 'gtk_ui.GtkUi', season_id: int, episode: 'models.Episode'=None):
|
||||
"""Show an dialog to create or edit episodes
|
||||
:param app: Reference to main UI application
|
||||
:param season_id: Is of the season in which the episode appears
|
||||
:param episode: Existing episode object to edit
|
||||
:return: Edited episode object, or None if the process was canceled
|
||||
"""
|
||||
# Set up the dialog
|
||||
dialog = builder.get_object("edit_episode_dialog") # type: Gtk.Dialog
|
||||
dialog.set_transient_for(builder.get_object("main_window"))
|
||||
dialog.set_title(title)
|
||||
with sql.db.atomic():
|
||||
if not episode:
|
||||
nxt_number = len(sql.Season.get_by_id(season_id).episodes) + 1
|
||||
episode = sql.Episode.create(seq_number=nxt_number, number=nxt_number, date=datetime.today(),
|
||||
season=season_id)
|
||||
# Set episode number
|
||||
builder.get_object("episode_no_spin_button").set_value(episode.number)
|
||||
# Set episode date
|
||||
builder.get_object('episode_calendar').select_month(episode.date.month, episode.date.year)
|
||||
builder.get_object('episode_calendar').select_day(episode.date.day)
|
||||
# Set participants for the episode
|
||||
builder.get_object('episode_players_store').clear()
|
||||
episode = models.Episode()
|
||||
episode.date = datetime.datetime.today()
|
||||
episode.number = 1
|
||||
episode.name = ''
|
||||
episode.players = []
|
||||
|
||||
app.ui.get_object('episode_name_entry').set_text(episode.name)
|
||||
app.ui.get_object('episode_no_spin_button').set_value(episode.number)
|
||||
app.ui.get_object('episode_calendar').select_month(episode.date.month, episode.date.year)
|
||||
app.ui.get_object('episode_calendar').select_day(episode.date.day)
|
||||
app.ui.get_object('episode_players_store').clear()
|
||||
for player in episode.players:
|
||||
builder.get_object('episode_players_store').append([player.id, player.name, player.hex_id])
|
||||
app.ui.get_object('episode_players_store').append([player.id, player.name, player.hex_id])
|
||||
|
||||
dialog = app.ui.get_object('edit_episode_dialog') # type: Gtk.Dialog
|
||||
result = dialog.run()
|
||||
dialog.hide()
|
||||
|
||||
if result != Gtk.ResponseType.OK:
|
||||
sql.db.rollback()
|
||||
return False
|
||||
return None
|
||||
|
||||
# Save all changes to Database
|
||||
player_ids = [row[0] for row in builder.get_object('episode_players_store')]
|
||||
# Insert new Players
|
||||
episode.players = sql.Player.select().where(sql.Player.id << player_ids)
|
||||
# Update Date of the Episode
|
||||
cal_value = builder.get_object('episode_calendar').get_date()
|
||||
selected_date = datetime(*cal_value).date()
|
||||
episode.date = selected_date,
|
||||
episode.number = int(builder.get_object("episode_no_spin_button").get_value())
|
||||
episode.name = builder.get_object("episode_name_entry").get_text()
|
||||
episode.save()
|
||||
return True
|
||||
episode.name = app.ui.get_object('episode_name_entry').get_text()
|
||||
episode.number = app.ui.get_object('episode_no_spin_button').get_value()
|
||||
cal_value = app.ui.get_object('episode_calendar').get_date()
|
||||
selected_date = datetime.datetime(*cal_value).date()
|
||||
episode.date = selected_date
|
||||
player_ids = [row[0] for row in app.ui.get_object('episode_players_store')]
|
||||
episode.players = [app.get_by_id(app.players, player_id) for player_id in player_ids]
|
||||
episode.season = season_id
|
||||
return episode
|
||||
|
||||
|
||||
def show_manage_players_dialog(builder: Gtk.Builder, title: str):
|
||||
"""Show a dialog for managing player base data.
|
||||
:param builder: Gtk.Builder object
|
||||
:param title: Title for the dialog
|
||||
def create_death(app: 'gtk_ui.GtkUi'):
|
||||
"""Show a dialog to create death events for an episode
|
||||
:param app: Main Gtk application
|
||||
:return: Death object or None if dialog was canceled
|
||||
"""
|
||||
dialog = builder.get_object("manage_players_dialog") # type: Gtk.Dialog
|
||||
dialog.set_transient_for(builder.get_object("main_window"))
|
||||
dialog.run()
|
||||
dialog.hide()
|
||||
|
||||
|
||||
def show_manage_enemies_dialog(builder: Gtk.Builder, season_id: int):
|
||||
dialog = builder.get_object("manage_enemies_dialog") # type: Gtk.Dialog
|
||||
dialog.set_transient_for(builder.get_object("main_window"))
|
||||
dialog.run()
|
||||
dialog.hide()
|
||||
|
||||
|
||||
def show_manage_drinks_dialog(builder: Gtk.Builder):
|
||||
dialog = builder.get_object("manage_drinks_dialog") # type: Gtk.Dialog
|
||||
dialog.set_transient_for(builder.get_object("main_window"))
|
||||
dialog.run()
|
||||
dialog.hide()
|
||||
|
||||
|
||||
def show_edit_death_dialog(builder: Gtk.Builder, episode_id: int, death: sql.Death=None):
|
||||
"""Show a dialog for editing or creating death events.
|
||||
:param builder: A Gtk.Builder object
|
||||
:param episode_id: ID to witch the death event belongs to
|
||||
:param death: (Optional) Death event witch should be edited
|
||||
:return: Gtk.ResponseType of the dialog
|
||||
"""
|
||||
dialog = builder.get_object("edit_death_dialog") # type: Gtk.Dialog
|
||||
dialog.set_transient_for(builder.get_object("main_window"))
|
||||
with sql.db.atomic():
|
||||
if death:
|
||||
index = util.get_index_of_combo_model(builder.get_object('edit_death_enemy_combo'), 0, death.enemy.id)
|
||||
builder.get_object('edit_death_enemy_combo').set_active(index)
|
||||
|
||||
# TODO Default drink should be set in config
|
||||
default_drink = sql.Drink.get().name
|
||||
store = builder.get_object('player_penalties_store')
|
||||
store.clear()
|
||||
for player in builder.get_object('episode_players_store'):
|
||||
store.append([None, player[1], default_drink, player[0]])
|
||||
|
||||
# Run the dialog
|
||||
dialog = app.ui.get_object("edit_death_dialog") # type: Gtk.Dialog
|
||||
result = dialog.run()
|
||||
dialog.hide()
|
||||
if result != Gtk.ResponseType.OK:
|
||||
sql.db.rollback()
|
||||
return result
|
||||
return None
|
||||
|
||||
# Collect info from widgets and save to database
|
||||
player_id = util.get_combo_value(builder.get_object('edit_death_player_combo'), 0)
|
||||
enemy_id = util.get_combo_value(builder.get_object('edit_death_enemy_combo'), 3)
|
||||
comment = builder.get_object('edit_death_comment_entry').get_text()
|
||||
if not death:
|
||||
death = sql.Death.create(episode=episode_id, player=player_id, enemy=enemy_id, info=comment)
|
||||
|
||||
store = builder.get_object('player_penalties_store')
|
||||
size = builder.get_object('edit_death_size_spin').get_value()
|
||||
death = models.Death()
|
||||
hour_spin = app.ui.get_object('death_hour_spin')
|
||||
min_spin = app.ui.get_object('death_min_spin')
|
||||
# Parse the inputs
|
||||
death.time = datetime.time(int(hour_spin.get_value()), int(min_spin.get_value()))
|
||||
death.enemy = util.get_combo_value(app.ui.get_object('edit_death_enemy_combo'), 4)
|
||||
death.player = util.get_combo_value(app.ui.get_object('edit_death_player_combo'), 0)
|
||||
death.info = app.ui.get_object('edit_death_comment_entry').get_text()
|
||||
death.episode = app.get_selected_episode_id()
|
||||
store = app.ui.get_object('player_penalties_store')
|
||||
size = app.ui.get_object('edit_death_size_spin').get_value()
|
||||
death.penalties = []
|
||||
for entry in store:
|
||||
drink_id = sql.Drink.get(sql.Drink.name == entry[2])
|
||||
sql.Penalty.create(size=size, player=entry[3], death=death.id, drink=drink_id)
|
||||
drink_id = [drink.id for drink in app.drinks.data if drink.name == entry[2]][0]
|
||||
penalty = models.Penalty({'id': entry[0], 'size': size, 'drink': drink_id, 'player': entry[3]})
|
||||
death.penalties.append(penalty)
|
||||
|
||||
return result
|
||||
return death
|
||||
|
||||
|
||||
def show_edit_victory_dialog(builder: Gtk.Builder, episode_id: int, victory: sql.Victory=None):
|
||||
"""Show a dialog for editing or creating victory events.
|
||||
:param builder: A Gtk.Builder object
|
||||
:param episode_id: ID to witch the victory event belongs to
|
||||
:param victory: (Optional) Victory event witch should be edited
|
||||
:return: Gtk.ResponseType of the dialog
|
||||
def create_victory(app: 'gtk_ui.GtkUi'):
|
||||
"""Show a dialog for creating victory events
|
||||
:param app: Reference to main gtk ui object
|
||||
:return: Created victory object or None, if canceled
|
||||
"""
|
||||
dialog = builder.get_object("edit_victory_dialog") # type: Gtk.Dialog
|
||||
dialog.set_transient_for(builder.get_object("main_window"))
|
||||
with sql.db.atomic():
|
||||
if victory:
|
||||
infos = [['edit_victory_player_combo', victory.player.id],
|
||||
['edit_victory_enemy_combo', victory.enemy.id]]
|
||||
for info in infos:
|
||||
combo = builder.get_object(info[0])
|
||||
index = util.get_index_of_combo_model(combo, 0, info[1])
|
||||
combo.set_active(index)
|
||||
builder.get_object('victory_comment_entry').set_text(victory.info)
|
||||
|
||||
# Run the dialog
|
||||
dialog = app.ui.get_object('edit_victory_dialog')
|
||||
result = dialog.run()
|
||||
dialog.hide()
|
||||
if result != Gtk.ResponseType.OK:
|
||||
sql.db.rollback()
|
||||
return result
|
||||
return None
|
||||
|
||||
# Collect info from widgets and save to database
|
||||
player_id = util.get_combo_value(builder.get_object('edit_victory_player_combo'), 0)
|
||||
enemy_id = util.get_combo_value(builder.get_object('edit_victory_enemy_combo'), 3)
|
||||
comment = builder.get_object('victory_comment_entry').get_text()
|
||||
if not victory:
|
||||
sql.Victory.create(episode=episode_id, player=player_id, enemy=enemy_id, info=comment)
|
||||
else:
|
||||
victory.player = player_id
|
||||
victory.enemy = enemy_id
|
||||
victory.info = comment
|
||||
victory.save()
|
||||
hour_spin = app.ui.get_object('vic_hour_spin')
|
||||
min_spin = app.ui.get_object('vic_min_spin')
|
||||
victory = models.Victory()
|
||||
victory.episode = app.get_selected_episode_id()
|
||||
victory.info = app.ui.get_object('victory_comment_entry').get_text()
|
||||
victory.player = util.get_combo_value(app.ui.get_object('edit_victory_player_combo'), 0)
|
||||
victory.enemy = util.get_combo_value(app.ui.get_object('edit_victory_enemy_combo'), 4)
|
||||
victory.time = datetime.time(int(hour_spin.get_value()), int(min_spin.get_value()))
|
||||
|
||||
return result
|
||||
return victory
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import gi
|
||||
import os
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from dsst_gtk3.handlers import handlers
|
||||
from dsst_gtk3 import util, reload
|
||||
from dsst_sql import sql, sql_func
|
||||
|
||||
from dsst_gtk3 import util, reload, client
|
||||
from common import models
|
||||
|
||||
class GtkUi:
|
||||
""" The main UI class """
|
||||
@@ -18,36 +17,90 @@ class GtkUi:
|
||||
]
|
||||
for path in glade_resources:
|
||||
self.ui.add_from_string(util.load_ui_resource_string(path))
|
||||
# Set the status bar logo
|
||||
dd_logo = ['dsst_gtk3', 'resources', 'images', 'dd.png']
|
||||
logo_pixbuf = util.load_image_resource(dd_logo, 60, 13)
|
||||
logo = self.ui.get_object('status_bar_logo').set_from_pixbuf(logo_pixbuf)
|
||||
# Connect signal handlers to UI
|
||||
self.handlers = handlers.Handlers(self)
|
||||
self.ui.connect_signals(self.handlers)
|
||||
# Show all widgets
|
||||
self.ui.get_object('main_window').show_all()
|
||||
db_config = config['sql_connections'][0]
|
||||
# Initialize the database
|
||||
sql.db.init(db_config['db_name'], host=db_config['host'], port=db_config['port'],
|
||||
user=db_config['user'], password=db_config['password'])
|
||||
# Show database info in status bar
|
||||
self.set_db_status_label(db_config)
|
||||
# Create database if not exists
|
||||
sql_func.create_tables()
|
||||
self.reload()
|
||||
# Connect to data server
|
||||
config = config['servers'][0]
|
||||
self.data_client = client.Access(config)
|
||||
# Create local data caches
|
||||
self.players = util.Cache()
|
||||
self.drinks = util.Cache()
|
||||
self.seasons = util.Cache()
|
||||
self.episodes = util.Cache()
|
||||
self.enemies = util.Cache()
|
||||
self.season_stats = util.Cache()
|
||||
# Create meta data cache
|
||||
self.meta = {'connection': '{}:{}'.format(config.get('host'), config.get('port'))}
|
||||
# Load base data and seasons
|
||||
self.load_server_meta()
|
||||
self.full_reload()
|
||||
self.update_status_bar_meta()
|
||||
|
||||
def reload(self):
|
||||
reload.reload_base_data(self.ui, self)
|
||||
def load_server_meta(self):
|
||||
self.meta['database'] = self.data_client.send_request('load_db_meta')
|
||||
|
||||
def full_reload(self):
|
||||
with util.network_operation(self):
|
||||
self.players.data = self.data_client.send_request('load_players')
|
||||
self.drinks.data = self.data_client.send_request('load_drinks')
|
||||
self.seasons.data = self.data_client.send_request('load_seasons')
|
||||
season_id = self.get_selected_season_id()
|
||||
if season_id:
|
||||
reload.reload_episodes(self.ui, self, season_id)
|
||||
reload.reload_season_stats(self.ui, self, season_id)
|
||||
else:
|
||||
return
|
||||
episode_id = self.get_selected_episode_id()
|
||||
if episode_id:
|
||||
reload.reload_episode_stats(self.ui, self, episode_id)
|
||||
self.episodes.data = self.data_client.send_request('load_episodes', season_id)
|
||||
self.season_stats.data = self.data_client.send_request('load_season_stats', season_id)
|
||||
cur_season = [s for s in self.seasons.data if s.id == season_id][0]
|
||||
self.enemies.data = cur_season.enemies
|
||||
reload.rebuild_view_data(self)
|
||||
|
||||
def set_db_status_label(self, db_conf: dict):
|
||||
self.ui.get_object('connection_label').set_text(f'{db_conf["user"]}@{db_conf["host"]}')
|
||||
self.ui.get_object('db_label').set_text(f'{db_conf["db_name"]}')
|
||||
def 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 '')
|
||||
|
||||
def get_selected_season_id(self) -> int:
|
||||
"""Read ID of the selected season from the UI
|
||||
@@ -63,6 +116,13 @@ class GtkUi:
|
||||
(model, tree_iter) = self.ui.get_object('episodes_tree_view').get_selection().get_selected()
|
||||
return model.get_value(tree_iter, 0) if tree_iter else None
|
||||
|
||||
@staticmethod
|
||||
def get_by_id(cache: 'util.Cache', object_id: int):
|
||||
try:
|
||||
return [x for x in cache.data if x.id == object_id][0]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
if not os.path.isfile(util.CONFIG_PATH):
|
||||
@@ -70,3 +130,7 @@ def main():
|
||||
config = util.load_config(util.CONFIG_PATH)
|
||||
GtkUi(config)
|
||||
Gtk.main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from dsst_gtk3 import dialogs, gtk_ui
|
||||
from dsst_sql import sql
|
||||
from common import models
|
||||
|
||||
|
||||
class BaseDataHandlers:
|
||||
@@ -7,48 +7,39 @@ class BaseDataHandlers:
|
||||
def __init__(self, app: 'gtk_ui.GtkUi'):
|
||||
self.app = app
|
||||
|
||||
def do_manage_players(self, *_):
|
||||
dialogs.show_manage_players_dialog(self.app.ui, 'Manage Players')
|
||||
|
||||
def do_add_player(self, entry):
|
||||
if entry.get_text():
|
||||
sql.Player.create(name=entry.get_text())
|
||||
self.app.update_player(models.Player({'name': entry.get_text()}))
|
||||
entry.set_text('')
|
||||
self.app.reload()
|
||||
|
||||
def do_manage_enemies(self, *_):
|
||||
dialogs.show_manage_enemies_dialog(self.app.ui, self.app.get_selected_season_id())
|
||||
|
||||
def on_player_name_edited(self, _, index, value):
|
||||
row = self.app.ui.get_object('all_players_store')[index]
|
||||
sql.Player.update(name=value)\
|
||||
.where(sql.Player.id == row[0])\
|
||||
.execute()
|
||||
self.app.reload()
|
||||
player = models.Player({'id': row[0],
|
||||
'name': value,
|
||||
'hex_id': row[2]})
|
||||
self.app.update_player(player)
|
||||
|
||||
def on_player_hex_edited(self, _, index, value):
|
||||
row = self.app.ui.get_object('all_players_store')[index]
|
||||
sql.Player.update(hex_id=value)\
|
||||
.where(sql.Player.id == row[0])\
|
||||
.execute()
|
||||
self.app.reload()
|
||||
player = models.Player({'id': row[0],
|
||||
'name': row[1],
|
||||
'hex_id': value})
|
||||
self.app.update_player(player)
|
||||
|
||||
def do_add_drink(self, entry):
|
||||
if entry.get_text():
|
||||
sql.Drink.create(name=entry.get_text(), vol=0)
|
||||
drink = models.Drink({'name': entry.get_text(), 'vol': 0.00})
|
||||
self.app.update_drink(drink)
|
||||
entry.set_text('')
|
||||
self.app.reload()
|
||||
|
||||
def on_drink_name_edited(self, _, index, value):
|
||||
row = self.app.ui.get_object('drink_store')[index]
|
||||
sql.Drink.update(name=value)\
|
||||
.where(sql.Drink.id == row[0])\
|
||||
.execute()
|
||||
self.app.reload()
|
||||
drink = [d for d in self.app.drinks.data if d.id == row[0]][0]
|
||||
drink.name = value
|
||||
self.app.update_drink(drink)
|
||||
|
||||
def on_drink_vol_edited(self, _, index, value):
|
||||
row = self.app.ui.get_object('drink_store')[index]
|
||||
sql.Drink.update(vol=value) \
|
||||
.where(sql.Drink.id == row[0]) \
|
||||
.execute()
|
||||
self.app.reload()
|
||||
drink = [d for d in self.app.drinks.data if d.id == row[0]][0]
|
||||
drink.vol = value
|
||||
self.app.update_drink(drink)
|
||||
@@ -11,9 +11,9 @@ class DeathHandlers:
|
||||
ep_id = self.app.get_selected_episode_id()
|
||||
if not ep_id:
|
||||
return
|
||||
result = dialogs.show_edit_death_dialog(self.app.ui, ep_id)
|
||||
if result == Gtk.ResponseType.OK:
|
||||
self.app.reload()
|
||||
death = dialogs.create_death(self.app)
|
||||
if death:
|
||||
self.app.save_death(death)
|
||||
|
||||
def on_penalty_drink_changed(self, _, path, text):
|
||||
self.app.ui.get_object('player_penalties_store')[path][2] = text
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from dsst_gtk3 import dialogs, util, gtk_ui
|
||||
from dsst_sql import sql
|
||||
import datetime
|
||||
|
||||
from dsst_gtk3 import dialogs, util, gtk_ui, reload
|
||||
from common import models
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class DialogHandlers:
|
||||
@@ -7,6 +10,11 @@ class DialogHandlers:
|
||||
def __init__(self, app: 'gtk_ui.GtkUi'):
|
||||
self.app = app
|
||||
|
||||
@staticmethod
|
||||
def do_run_manage_dialog(dialog: 'Gtk.Dialog'):
|
||||
dialog.run()
|
||||
dialog.hide()
|
||||
|
||||
def do_add_player_to_episode(self, combo):
|
||||
""" Signal Handler for Add Player to Episode Button in Manage Episode Dialog
|
||||
:param combo: Combo box with all the available players
|
||||
@@ -14,7 +22,7 @@ class DialogHandlers:
|
||||
player_id = util.get_combo_value(combo, 0)
|
||||
if player_id:
|
||||
self.app.ui.get_object('add_player_combo_box').set_active(-1)
|
||||
player = sql.Player.get(sql.Player.id == player_id)
|
||||
player = self.app.get_by_id(self.app.players, player_id)
|
||||
store = self.app.ui.get_object('episode_players_store')
|
||||
if not any(row[0] == player_id for row in store):
|
||||
store.append([player_id, player.name, player.hex_id])
|
||||
@@ -22,9 +30,41 @@ class DialogHandlers:
|
||||
def do_add_enemy(self, entry):
|
||||
if entry.get_text():
|
||||
store = self.app.ui.get_object('enemy_season_store')
|
||||
enemy = sql.Enemy.create(name=entry.get_text(), season=self.app.get_selected_season_id())
|
||||
store.append([enemy.name, False, 0, enemy.id])
|
||||
enemy = models.Enemy()
|
||||
enemy.name = entry.get_text()
|
||||
enemy.season = self.app.get_selected_season_id()
|
||||
enemy.boss = not self.app.ui.get_object('enemy_optional_ckeck').get_active()
|
||||
self.app.ui.get_object('enemy_optional_ckeck').set_active(False)
|
||||
entry.set_text('')
|
||||
|
||||
def do_manage_drinks(self, *_):
|
||||
result = dialogs.show_manage_drinks_dialog(self.app.ui)
|
||||
self.app.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)
|
||||
|
||||
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)
|
||||
|
||||
def do_show_date_picker(self, entry: 'Gtk.Entry', *_):
|
||||
dialog = self.app.ui.get_object('date_picker_dialog')
|
||||
result = dialog.run()
|
||||
dialog.hide()
|
||||
if result == Gtk.ResponseType.OK:
|
||||
date = self.app.ui.get_object('date_picker_calendar').get_date()
|
||||
date_string = '{}-{:02d}-{:02d}'.format(date.year, date.month +1, date.day)
|
||||
entry.set_text(date_string)
|
||||
|
||||
@staticmethod
|
||||
def do_set_today(cal: 'Gtk.Calendar'):
|
||||
"""Set date of a Gtk Calendar to today
|
||||
:param cal: Gtk.Calendar
|
||||
"""
|
||||
cal.select_month = datetime.date.today().month
|
||||
cal.select_day = datetime.date.today().day
|
||||
|
||||
@@ -6,7 +6,6 @@ from dsst_gtk3.handlers.base_data_handlers import BaseDataHandlers
|
||||
from dsst_gtk3.handlers.dialog_handlers import DialogHandlers
|
||||
from dsst_gtk3.handlers.death_handlers import DeathHandlers
|
||||
from dsst_gtk3.handlers.victory_handlers import VictoryHandlers
|
||||
from dsst_sql import sql, sql_func
|
||||
|
||||
|
||||
class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers, VictoryHandlers):
|
||||
@@ -28,12 +27,10 @@ class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers,
|
||||
""" Signal will be sent when app should close
|
||||
:param _: Arguments to the delete event
|
||||
"""
|
||||
sql.db.close()
|
||||
Gtk.main_quit()
|
||||
|
||||
# DEBUG Functions ##################################################################################################
|
||||
|
||||
@staticmethod
|
||||
def do_delete_database(*_):
|
||||
sql_func.drop_tables()
|
||||
sql_func.create_tables()
|
||||
pass
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from dsst_sql import sql
|
||||
from dsst_gtk3 import dialogs, gtk_ui
|
||||
from dsst_gtk3 import dialogs, gtk_ui, reload
|
||||
|
||||
|
||||
class SeasonHandlers:
|
||||
@@ -8,23 +7,27 @@ class SeasonHandlers:
|
||||
self.app = app
|
||||
|
||||
def do_add_season(self, *_):
|
||||
name = dialogs.enter_string_dialog(self.app.ui, 'Name for the new Season')
|
||||
if name:
|
||||
sql.Season.create(game_name=name, number=1)
|
||||
self.app.reload()
|
||||
season = dialogs.edit_season(self.app.ui)
|
||||
if season:
|
||||
self.app.update_season(season)
|
||||
self.app.full_reload()
|
||||
|
||||
def do_season_selected(self, *_):
|
||||
self.app.reload()
|
||||
self.app.episodes.valid = False
|
||||
self.app.season_stats.valid = False
|
||||
self.app.full_reload()
|
||||
|
||||
def do_add_episode(self, *_):
|
||||
season_id = self.app.get_selected_season_id()
|
||||
if not season_id:
|
||||
return
|
||||
dialogs.show_episode_dialog(self.app.ui, 'Create new Episode', season_id)
|
||||
self.app.reload()
|
||||
ep = dialogs.edit_episode(self.app, season_id)
|
||||
if ep:
|
||||
self.app.update_episode(ep)
|
||||
self.app.full_reload()
|
||||
|
||||
def on_selected_episode_changed(self, *_):
|
||||
self.app.reload()
|
||||
reload.reload_episode_stats(self.app)
|
||||
|
||||
def on_episode_double_click(self, *_):
|
||||
self.app.ui.get_object('stats_notebook').set_current_page(1)
|
||||
|
||||
@@ -11,6 +11,6 @@ class VictoryHandlers:
|
||||
ep_id = self.app.get_selected_episode_id()
|
||||
if not ep_id:
|
||||
return
|
||||
result = dialogs.show_edit_victory_dialog(self.app.ui, ep_id)
|
||||
if result == Gtk.ResponseType.OK:
|
||||
self.app.reload()
|
||||
victory = dialogs.create_victory(self.app)
|
||||
if victory:
|
||||
self.app.save_victory(victory)
|
||||
|
||||
@@ -1,116 +1,123 @@
|
||||
from collections import Counter
|
||||
from gi.repository import Gtk
|
||||
from dsst_gtk3 import gtk_ui
|
||||
from dsst_sql import sql, sql_func
|
||||
from dsst_gtk3 import util
|
||||
from dsst_gtk3 import util, gtk_ui
|
||||
|
||||
|
||||
def reload_base_data(builder: Gtk.Builder, app: 'gtk_ui.GtkUi'):
|
||||
def reload_base_data(app: 'gtk_ui.GtkUi',):
|
||||
"""Reload function for all base data witch is not dependant on a selected season or episode
|
||||
:param app: GtkUi instance
|
||||
:param builder: Gtk.Builder with loaded UI
|
||||
"""
|
||||
# Rebuild all players store
|
||||
builder.get_object('all_players_store').clear()
|
||||
for player in sql.Player.select():
|
||||
builder.get_object('all_players_store').append([player.id, player.name, player.hex_id])
|
||||
app.ui.get_object('all_players_store').clear()
|
||||
for player in app.players.data:
|
||||
app.ui.get_object('all_players_store').append([player.id, player.name, player.hex_id])
|
||||
# Rebuild drink store
|
||||
builder.get_object('drink_store').clear()
|
||||
for drink in sql.Drink.select():
|
||||
builder.get_object('drink_store').append([drink.id, drink.name, '{:.2f}%'.format(drink.vol)])
|
||||
app.ui.get_object('drink_store').clear()
|
||||
for drink in app.drinks.data:
|
||||
app.ui.get_object('drink_store').append([drink.id, drink.name, '{:.2f}%'.format(drink.vol)])
|
||||
# Rebuild seasons store
|
||||
combo = builder.get_object('season_combo_box') # type: Gtk.ComboBox
|
||||
combo = app.ui.get_object('season_combo_box') # type: Gtk.ComboBox
|
||||
active = combo.get_active()
|
||||
with util.block_handler(combo, app.handlers.do_season_selected):
|
||||
store = builder.get_object('seasons_store')
|
||||
store = app.ui.get_object('seasons_store')
|
||||
store.clear()
|
||||
for season in sql.Season.select().order_by(sql.Season.number):
|
||||
for season in app.seasons.data:
|
||||
store.append([season.id, season.game_name])
|
||||
combo.set_active(active)
|
||||
|
||||
|
||||
def reload_episodes(builder: Gtk.Builder, app: 'gtk_ui.GtkUi', season_id: int):
|
||||
def reload_episodes(app: 'gtk_ui.GtkUi'):
|
||||
"""Reload all data that is dependant on a selected season
|
||||
:param app: GtkUi instance
|
||||
:param builder: Gtk.Builder with loaded UI
|
||||
:param season_id: ID of the season for witch to load data
|
||||
"""
|
||||
# Rebuild episodes store
|
||||
selection = builder.get_object('episodes_tree_view').get_selection()
|
||||
if not app.get_selected_season_id(): return
|
||||
selection = app.ui.get_object('episodes_tree_view').get_selection()
|
||||
with util.block_handler(selection, app.handlers.on_selected_episode_changed):
|
||||
model, selected_paths = selection.get_selected_rows()
|
||||
model.clear()
|
||||
for episode in sql_func.get_episodes_for_season(season_id):
|
||||
for episode in app.episodes.data:
|
||||
model.append([episode.id, episode.name, str(episode.date), episode.number])
|
||||
if selected_paths:
|
||||
selection.select_path(selected_paths[0])
|
||||
|
||||
|
||||
def reload_season_stats(builder: Gtk.Builder, app: 'gtk_ui.GtkUi', season_id: int):
|
||||
def reload_season_stats(app: 'gtk_ui.GtkUi'):
|
||||
"""Load statistic data for selected season
|
||||
:param builder: Gtk.Builder with loaded UI
|
||||
:param app: GtkUi instance
|
||||
:param season_id: ID of the season for witch to load data
|
||||
"""
|
||||
player_stats = {}
|
||||
for episode in sql_func.get_episodes_for_season(season_id):
|
||||
for player in episode.players:
|
||||
player_stats[player.name] = [sql_func.get_player_deaths_for_season(season_id, player.id),
|
||||
sql_func.get_player_victories_for_season(season_id, player.id)]
|
||||
store = builder.get_object('player_season_store')
|
||||
if not app.season_stats.valid: return
|
||||
season_stats = app.season_stats.data
|
||||
# Load player kill/death data
|
||||
store = app.ui.get_object('player_season_store')
|
||||
store.clear()
|
||||
for name, stats in player_stats.items():
|
||||
store.append([name, stats[0], stats[1]])
|
||||
for player_name, kills, deaths in season_stats.player_kd:
|
||||
store.append([player_name, deaths, kills])
|
||||
|
||||
# Load enemy stats for season
|
||||
season = sql.Season.get(sql.Season.id == season_id)
|
||||
enemy_stats = {
|
||||
enemy.name: [False, len(sql.Death.select().where(sql.Death.enemy == enemy)), enemy.id]
|
||||
for enemy in season.enemies}
|
||||
store = builder.get_object('enemy_season_store')
|
||||
store = app.ui.get_object('enemy_season_store')
|
||||
store.clear()
|
||||
for name, stats in enemy_stats.items():
|
||||
store.append([name, stats[0], stats[1], stats[2]])
|
||||
for enemy_id, enemy_name, deaths, defeated, boss in season_stats.enemies:
|
||||
store.append([enemy_name, defeated, deaths, boss, enemy_id])
|
||||
|
||||
|
||||
def reload_episode_stats(builder: Gtk.Builder, app: 'gtk_ui.GtkUi', episode_id: int):
|
||||
def reload_episode_stats(app: 'gtk_ui.GtkUi'):
|
||||
"""Reload all data that is dependant on a selected episode
|
||||
:param builder: builder: Gtk.Builder with loaded UI
|
||||
:param app: app: GtkUi instance
|
||||
:param episode_id: ID of the episode for witch to load data
|
||||
"""
|
||||
episode = sql.Episode.get(sql.Episode.id == episode_id)
|
||||
store = builder.get_object('episode_players_store')
|
||||
ep_id = app.get_selected_episode_id()
|
||||
if not app.episodes.valid or not ep_id: return
|
||||
episode = [ep for ep in app.episodes.data if ep.id == ep_id][0]
|
||||
store = app.ui.get_object('episode_players_store')
|
||||
store.clear()
|
||||
for player in episode.players:
|
||||
store.append([player.id, player.name, player.hex_id])
|
||||
# Reload death store for notebook view
|
||||
store = builder.get_object('episode_deaths_store')
|
||||
store = app.ui.get_object('episode_deaths_store')
|
||||
store.clear()
|
||||
for death in episode.deaths:
|
||||
penalties = [x.drink.name for x in death.penalties]
|
||||
penalties = [f'{number}x {drink}' for drink, number in Counter(penalties).items()]
|
||||
penalties = ['{}x {}'.format(number, drink) for drink, number in Counter(penalties).items()]
|
||||
penalty_string = ', '.join(penalties)
|
||||
store.append([death.id, death.player.name, death.enemy.name, penalty_string])
|
||||
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])
|
||||
# Reload victory store for notebook view
|
||||
store = builder.get_object('episode_victories_store')
|
||||
store = app.ui.get_object('episode_victories_store')
|
||||
store.clear()
|
||||
for victory in episode.victories:
|
||||
store.append([victory.id, victory.player.name, victory.enemy.name, victory.info])
|
||||
time_string = '{:02d}:{:02d}'.format(victory.time.hour, victory.time.minute)
|
||||
store.append([victory.id, victory.player.name, victory.enemy.name, victory.info, time_string])
|
||||
|
||||
# Stat grid
|
||||
builder.get_object('ep_stat_title').set_text('Stats for episode {}\n{}'.format(episode.number, episode.name))
|
||||
builder.get_object('ep_death_count_label').set_text(str(len(episode.deaths)))
|
||||
app.ui.get_object('ep_stat_title').set_text('Stats for episode {}\n{}'.format(episode.number, episode.name))
|
||||
app.ui.get_object('ep_death_count_label').set_text(str(len(episode.deaths)))
|
||||
drink_count = sum(len(death.penalties) for death in episode.deaths)
|
||||
builder.get_object('ep_drinks_label').set_text(str(drink_count))
|
||||
builder.get_object('ep_player_drinks_label').set_text(str(len(episode.deaths)))
|
||||
app.ui.get_object('ep_drinks_label').set_text(str(drink_count))
|
||||
app.ui.get_object('ep_player_drinks_label').set_text(str(len(episode.deaths)))
|
||||
dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths)
|
||||
l_booze = round(dl_booze / 10, 2)
|
||||
builder.get_object('ep_booze_label').set_text('{}l'.format(l_booze))
|
||||
app.ui.get_object('ep_booze_label').set_text('{}l'.format(l_booze))
|
||||
dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths)
|
||||
ml_booze = round(dl_booze * 10, 0)
|
||||
builder.get_object('ep_player_booze_label').set_text('{}ml'.format(ml_booze))
|
||||
app.ui.get_object('ep_player_booze_label').set_text('{}ml'.format(ml_booze))
|
||||
enemy_list = [death.enemy.name for death in episode.deaths]
|
||||
sorted_list = Counter(enemy_list).most_common(1)
|
||||
if sorted_list:
|
||||
enemy_name, deaths = sorted_list[0]
|
||||
builder.get_object('ep_enemy_name_label').set_text(f'{enemy_name} ({deaths} Deaths)')
|
||||
app.ui.get_object('ep_enemy_name_label').set_text('{} ({} Deaths)'.format(enemy_name, deaths))
|
||||
|
||||
|
||||
def fill_list_store(store: Gtk.ListStore, models: list):
|
||||
store.clear()
|
||||
for model in models:
|
||||
pass
|
||||
|
||||
|
||||
def rebuild_view_data(app: 'gtk_ui.GtkUi'):
|
||||
reload_base_data(app)
|
||||
reload_episodes(app)
|
||||
reload_episode_stats(app)
|
||||
reload_season_stats(app)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.20.3 -->
|
||||
<!-- Generated with glade 3.20.4 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<requires lib="gtk+" version="3.16"/>
|
||||
<object class="GtkDialog" id="nameEnterDialog">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
BIN
dsst/dsst_gtk3/resources/images/dd.png
Normal file
BIN
dsst/dsst_gtk3/resources/images/dd.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@@ -4,23 +4,38 @@ This modules contains general utilities for the GTK application to use.
|
||||
import json
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gtk, GdkPixbuf
|
||||
from typing import Callable
|
||||
from dsst_gtk3 import gtk_ui
|
||||
from zipfile import ZipFile
|
||||
|
||||
CONFIG_PATH = os.path.join(os.path.expanduser('~'), '.config', 'dsst', 'config.json')
|
||||
DEFAULT_CONFIG = {
|
||||
'auto_connect': False,
|
||||
'sql_connections': [{
|
||||
'servers': [{
|
||||
'host': 'localhost',
|
||||
'port': 3306,
|
||||
'db_name': 'dsst',
|
||||
'user': 'dsst',
|
||||
'password': 'dsst'}
|
||||
'port': 12345,
|
||||
'buffer_size': 1024,
|
||||
'auth_token': ''}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class Cache:
|
||||
def __init__(self, data={}, valid=False):
|
||||
self._data = data
|
||||
self.valid = valid
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@data.setter
|
||||
def data(self, value):
|
||||
self._data = value
|
||||
self.valid = True
|
||||
|
||||
|
||||
@contextmanager
|
||||
def block_handler(widget: 'Gtk.Widget', handler_func: Callable):
|
||||
"""Run an operation while a signal handler for a widget is blocked
|
||||
@@ -32,6 +47,21 @@ def block_handler(widget: 'Gtk.Widget', handler_func: Callable):
|
||||
widget.handler_unblock_by_func(handler_func)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def network_operation(app: 'gtk_ui.GtkUi'):
|
||||
"""Run operation in try/except block and display exception in a dialog
|
||||
:param app: Reference to main Gtk Application
|
||||
"""
|
||||
app.ui.get_object('status_bar').push(0, 'Connecting to server')
|
||||
try:
|
||||
yield
|
||||
except Exception as e:
|
||||
print(e)
|
||||
app.ui.get_object('status_bar').push(0, str(e))
|
||||
else:
|
||||
app.ui.get_object('status_bar').push(0, '')
|
||||
|
||||
|
||||
def get_combo_value(combo, index: int):
|
||||
""" Retrieve the selected value of a combo box at the selected index in the model
|
||||
:param combo: Any Gtk Widget that supports 'get_active_iter()'
|
||||
@@ -59,7 +89,7 @@ def get_index_of_combo_model(widget, column: int, value: int):
|
||||
def load_ui_resource_from_file(resource_path: list) -> str:
|
||||
project_base_dir = os.path.dirname(os.path.dirname(__file__))
|
||||
full_path = os.path.join(project_base_dir, *resource_path)
|
||||
with open(full_path, 'r') as file:
|
||||
with open(full_path, 'r', encoding='utf8') as file:
|
||||
return file.read()
|
||||
|
||||
|
||||
@@ -77,10 +107,35 @@ def load_ui_resource_string(resource_path: list) -> str:
|
||||
if os.path.isdir(os.path.dirname(__file__)):
|
||||
return load_ui_resource_from_file(resource_path)
|
||||
else:
|
||||
|
||||
return load_ui_resource_from_archive(resource_path)
|
||||
|
||||
|
||||
def load_image_resource_file(resource_path: list, width: int, height: int) -> GdkPixbuf:
|
||||
project_base_dir = os.path.dirname(os.path.dirname(__file__))
|
||||
full_path = os.path.join(project_base_dir, *resource_path)
|
||||
return GdkPixbuf.Pixbuf.new_from_file_at_scale(full_path, width=width, height=height, preserve_aspect_ratio=False)
|
||||
|
||||
|
||||
def load_image_resource_archive(resource_path: list, width: int, height: int) -> GdkPixbuf:
|
||||
resource_path = os.path.join(*resource_path)
|
||||
zip_path = os.path.dirname(os.path.dirname(__file__))
|
||||
with ZipFile(zip_path, 'r') as archive:
|
||||
with archive.open(resource_path) as data:
|
||||
loader = GdkPixbuf.PixbufLoader()
|
||||
loader.write(data.read())
|
||||
pixbuf = loader.get_pixbuf() # type: GdkPixbuf.Pixbuf
|
||||
pixbuf = pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR)
|
||||
loader.close()
|
||||
return pixbuf
|
||||
|
||||
|
||||
def load_image_resource(resource_path: list, width: int, height: int) -> GdkPixbuf:
|
||||
if os.path.isdir(os.path.dirname(__file__)):
|
||||
return load_image_resource_file(resource_path, width, height)
|
||||
else:
|
||||
return load_image_resource_archive(resource_path, width, height)
|
||||
|
||||
|
||||
def load_config(config_path: str) -> dict:
|
||||
with open(config_path) as config_file:
|
||||
return json.load(config_file)
|
||||
|
||||
0
dsst/dsst_server/__init__.py
Normal file
0
dsst/dsst_server/__init__.py
Normal file
11
dsst/dsst_server/__main__.py
Normal file
11
dsst/dsst_server/__main__.py
Normal 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()
|
||||
0
dsst/dsst_server/data_access/__init__.py
Normal file
0
dsst/dsst_server/data_access/__init__.py
Normal file
70
dsst/dsst_server/data_access/mapping.py
Normal file
70
dsst/dsst_server/data_access/mapping.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from dsst_server.data_access import sql
|
||||
from common import models
|
||||
|
||||
|
||||
def map_base_fields(cls, db_model):
|
||||
"""Automatically map fields of db models to common models
|
||||
:param cls: common.models class to create
|
||||
:param db_model: database model from which to map
|
||||
:return: An common.models object
|
||||
"""
|
||||
model = cls()
|
||||
attrs = [attr for attr in db_model._meta.fields]
|
||||
for attr in attrs:
|
||||
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
|
||||
|
||||
|
||||
def db_to_drink(drink: 'sql.Drink'):
|
||||
return map_base_fields(models.Drink, drink)
|
||||
|
||||
|
||||
def db_to_enemy(enemy: 'sql.Enemy'):
|
||||
return map_base_fields(models.Enemy, enemy)
|
||||
|
||||
|
||||
def db_to_player(player: 'sql.Player'):
|
||||
return map_base_fields(models.Player, player)
|
||||
|
||||
|
||||
def db_to_penalty(penalty: 'sql.Penalty'):
|
||||
model = map_base_fields(models.Penalty, penalty)
|
||||
model.drink = db_to_drink(penalty.drink)
|
||||
model.player = db_to_player(penalty.player)
|
||||
return model
|
||||
|
||||
|
||||
def db_to_death(death: 'sql.Death'):
|
||||
model = map_base_fields(models.Death, death)
|
||||
model.player = db_to_player(death.player)
|
||||
model.enemy = db_to_enemy(death.enemy)
|
||||
model.penalties = [db_to_penalty(penalty) for penalty in death.penalties]
|
||||
return model
|
||||
|
||||
|
||||
def db_to_victory(victory: 'sql.Victory'):
|
||||
model = map_base_fields(models.Victory, victory)
|
||||
model.player = db_to_player(victory.player)
|
||||
model.enemy = db_to_enemy(victory.enemy)
|
||||
return model
|
||||
|
||||
|
||||
def db_to_episode(episode: 'sql.Episode'):
|
||||
model = map_base_fields(models.Episode, episode)
|
||||
model.players = [db_to_player(player) for player in episode.players]
|
||||
model.deaths = [db_to_death(death) for death in episode.deaths]
|
||||
model.victories = [db_to_victory(victory) for victory in episode.victories]
|
||||
return model
|
||||
|
||||
|
||||
def db_to_season(season: 'sql.Season'):
|
||||
model = map_base_fields(models.Season, season)
|
||||
model.enemies = [db_to_enemy(enemy) for enemy in season.enemies]
|
||||
model.episodes = [db_to_episode(ep) for ep in season.episodes]
|
||||
return model
|
||||
@@ -5,8 +5,14 @@ Example:
|
||||
from sql import Episode
|
||||
query = Episode.select().where(Episode.name == 'MyName')
|
||||
"""
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
except ImportError:
|
||||
print('peewee package not installed')
|
||||
sys.exit(0)
|
||||
|
||||
db = MySQLDatabase(None)
|
||||
|
||||
@@ -56,7 +62,7 @@ class Drink(Model):
|
||||
class Enemy(Model):
|
||||
id = AutoField()
|
||||
name = CharField()
|
||||
optional = BooleanField()
|
||||
boss = BooleanField()
|
||||
season = ForeignKeyField(Season, backref='enemies')
|
||||
|
||||
class Meta:
|
||||
@@ -66,6 +72,7 @@ class Enemy(Model):
|
||||
class Death(Model):
|
||||
id = AutoField()
|
||||
info = CharField(null=True)
|
||||
time = TimeField(default=datetime.time(0, 0))
|
||||
player = ForeignKeyField(Player)
|
||||
enemy = ForeignKeyField(Enemy)
|
||||
episode = ForeignKeyField(Episode, backref='deaths')
|
||||
@@ -88,6 +95,7 @@ class Penalty(Model):
|
||||
class Victory(Model):
|
||||
id = AutoField()
|
||||
info = CharField(null=True)
|
||||
time = TimeField(default=datetime.time(0, 0))
|
||||
player = ForeignKeyField(Player)
|
||||
enemy = ForeignKeyField(Enemy)
|
||||
episode = ForeignKeyField(Episode, backref='victories')
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
This module contains shorthand functions for common queries to ease access from the UI
|
||||
"""
|
||||
from dsst_sql.sql import *
|
||||
from dsst_server.data_access import sql
|
||||
|
||||
|
||||
def get_episodes_for_season(season_id: int) -> list:
|
||||
@@ -10,8 +10,8 @@ def get_episodes_for_season(season_id: int) -> list:
|
||||
:return: List of sql.Episode or empty list
|
||||
"""
|
||||
try:
|
||||
return list(Season.get(Season.id == season_id).episodes)
|
||||
except Episode.DoesNotExist:
|
||||
return list(sql.Season.get(sql.Season.id == season_id).episodes)
|
||||
except sql.Episode.DoesNotExist:
|
||||
return []
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ def get_player_deaths_for_season(season_id: int, player_id: int) -> int:
|
||||
:return: Number of deaths of the player in the season
|
||||
"""
|
||||
deaths = 0
|
||||
for episode in list(Season.get(Season.id == season_id).episodes):
|
||||
for episode in list(sql.Season.get(sql.Season.id == season_id).episodes):
|
||||
deaths = deaths + len([death for death in list(episode.deaths) if death.player.id == player_id])
|
||||
return deaths
|
||||
|
||||
@@ -34,19 +34,33 @@ def get_player_victories_for_season(season_id: int, player_id: int) -> int:
|
||||
:return: Number of all victories of the player in the season
|
||||
"""
|
||||
victories = 0
|
||||
for episode in list(Season.get(Season.id == season_id).episodes):
|
||||
for episode in list(sql.Season.get(sql.Season.id == season_id).episodes):
|
||||
victories = victories + len([vic for vic in list(episode.victories) if vic.player.id == player_id])
|
||||
return victories
|
||||
|
||||
|
||||
def players_for_season(season_id: int) -> set:
|
||||
season_eps = list(sql.Season.get(sql.Season.id == season_id).episodes)
|
||||
players = set()
|
||||
for ep in season_eps:
|
||||
players.update([player for player in ep.players])
|
||||
return players
|
||||
|
||||
|
||||
def enemy_attempts(enemy_id: int) -> int:
|
||||
return sql.Death.select().where(sql.Death.enemy == enemy_id).count()
|
||||
|
||||
|
||||
def create_tables():
|
||||
"""Create all database tables"""
|
||||
models = [Season, Episode, Player, Drink, Enemy, Death, Victory, Penalty, Episode.players.get_through_model()]
|
||||
models = [sql.Season, sql.Episode, sql.Player, sql.Drink, sql.Enemy, sql.Death, sql.Victory, sql.Penalty,
|
||||
sql.Episode.players.get_through_model()]
|
||||
for model in models:
|
||||
model.create_table()
|
||||
|
||||
|
||||
def drop_tables():
|
||||
"""Drop all data in database"""
|
||||
models = [Season, Episode, Player, Drink, Enemy, Death, Victory, Penalty, Episode.players.get_through_model()]
|
||||
db.drop_tables(models)
|
||||
models = [sql.Season, sql.Episode, sql.Player, sql.Drink, sql.Enemy, sql.Death, sql.Victory, sql.Penalty,
|
||||
sql.Episode.players.get_through_model()]
|
||||
sql.db.drop_tables(models)
|
||||
6
dsst/dsst_server/func_proxy.py
Normal file
6
dsst/dsst_server/func_proxy.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from dsst_server.func_write import WriteFunctions
|
||||
from dsst_server.func_read import ReadFunctions
|
||||
|
||||
|
||||
class FunctionProxy(WriteFunctions, ReadFunctions):
|
||||
pass
|
||||
52
dsst/dsst_server/func_read.py
Normal file
52
dsst/dsst_server/func_read.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from dsst_server.data_access import sql, sql_func, mapping
|
||||
from common import models
|
||||
from playhouse import shortcuts
|
||||
|
||||
|
||||
class ReadFunctions:
|
||||
@staticmethod
|
||||
def load_db_meta(*_):
|
||||
return sql.db.database
|
||||
|
||||
@staticmethod
|
||||
def load_seasons(*_):
|
||||
return [mapping.db_to_season(season) for season in sql.Season.select()]
|
||||
|
||||
@staticmethod
|
||||
def load_seasons_all(*_):
|
||||
return [shortcuts.model_to_dict(season, backrefs=True, max_depth=2) for season in sql.Season.select()]
|
||||
|
||||
@staticmethod
|
||||
def load_episodes(season_id, *_):
|
||||
if not season_id:
|
||||
raise Exception('Exception: Missing argument (season_id)')
|
||||
return [mapping.db_to_episode(ep) for ep in sql.Season.get(sql.Season.id == season_id).episodes]
|
||||
|
||||
@staticmethod
|
||||
def load_players(*_):
|
||||
return [mapping.db_to_player(player) for player in sql.Player.select()]
|
||||
|
||||
@staticmethod
|
||||
def load_enemies(season_id, *_):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def load_drinks(*_):
|
||||
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.id,
|
||||
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
|
||||
78
dsst/dsst_server/func_write.py
Normal file
78
dsst/dsst_server/func_write.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from common import models
|
||||
from dsst_server.data_access import sql
|
||||
|
||||
|
||||
class WriteFunctions:
|
||||
@staticmethod
|
||||
def create_season(season: 'models.Season'):
|
||||
return 'Season created.'
|
||||
|
||||
@staticmethod
|
||||
def update_enemy(enemy: 'models.Enemy', *_):
|
||||
(sql.Enemy
|
||||
.insert(id=enemy.id, boss=enemy.boss, name=enemy.name, season=enemy.season)
|
||||
.on_conflict(update={sql.Enemy.name: enemy.name,
|
||||
sql.Enemy.boss: enemy.boss,
|
||||
sql.Enemy.season: enemy.season})
|
||||
.execute())
|
||||
|
||||
@staticmethod
|
||||
def update_player(player: 'models.Player', *_):
|
||||
(sql.Player
|
||||
.insert(id=player.id, name=player.name, hex_id=player.hex_id)
|
||||
.on_conflict(update={sql.Player.name: player.name,
|
||||
sql.Player.hex_id: player.hex_id})
|
||||
.execute())
|
||||
|
||||
@staticmethod
|
||||
def update_drink(drink: 'models.Drink', *_):
|
||||
(sql.Drink
|
||||
.insert(id=drink.id, name=drink.name, vol=drink.vol)
|
||||
.on_conflict(update={sql.Drink.name: drink.name,
|
||||
sql.Drink.vol: drink.vol})
|
||||
.execute())
|
||||
|
||||
@staticmethod
|
||||
def save_death(death: 'models.Death'):
|
||||
with sql.db.atomic():
|
||||
created_id = (sql.Death
|
||||
.insert(info=death.info, player=death.player, enemy=death.enemy, episode=death.episode,
|
||||
time=death.time)
|
||||
.execute())
|
||||
for penalty in death.penalties:
|
||||
sql.Penalty.create(death=created_id, size=penalty.size, drink=penalty.drink, player=penalty.player)
|
||||
|
||||
@staticmethod
|
||||
def save_victory(victory: 'models.Victory'):
|
||||
(sql.Victory
|
||||
.insert(info=victory.info, player=victory.player, enemy=victory.enemy, time=victory.time,
|
||||
episode=victory.episode, id=victory.id)
|
||||
.execute())
|
||||
|
||||
@staticmethod
|
||||
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,
|
||||
end_date=season.end_date)
|
||||
.on_conflict(
|
||||
update={sql.Season.number: season.number,
|
||||
sql.Season.game_name: season.game_name,
|
||||
sql.Season.start_date: season.start_date,
|
||||
sql.Season.end_date: season.end_date})
|
||||
.execute())
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
.insert(id=episode.id, number=episode.number, seq_number=episode.number, name=episode.name,
|
||||
date=episode.date, season=episode.season)
|
||||
.on_conflict(update={sql.Episode.name: episode.name,
|
||||
sql.Episode.seq_number: episode.seq_number,
|
||||
sql.Episode.number: episode.number,
|
||||
sql.Episode.date: episode.date,
|
||||
sql.Episode.season: episode.season})
|
||||
.execute())
|
||||
db_episode = sql.Episode.get(sql.Episode.id == new_ep_id)
|
||||
db_episode.players = players
|
||||
db_episode.save()
|
||||
99
dsst/dsst_server/server.py
Normal file
99
dsst/dsst_server/server.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import json
|
||||
import pickle
|
||||
import socket
|
||||
|
||||
import sys
|
||||
|
||||
import os
|
||||
|
||||
from common import util, models
|
||||
from dsst_server import func_read, func_write, tokens
|
||||
from dsst_server.func_proxy import FunctionProxy
|
||||
from dsst_server.data_access import sql, sql_func
|
||||
|
||||
PORT = 12345
|
||||
HOST = socket.gethostname()
|
||||
BUFFER_SIZE = 1024
|
||||
|
||||
|
||||
class DsstServer:
|
||||
def __init__(self, config={}):
|
||||
self.socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
print('Created socket')
|
||||
|
||||
self.socket_server.bind((HOST, PORT))
|
||||
print('Bound socket to {} on host {}'.format(PORT, HOST))
|
||||
|
||||
# Initialize database
|
||||
db_config = config.get('database')
|
||||
sql.db.init(db_config.get('db_name'), user=db_config.get('user'), password=db_config.get('password'))
|
||||
sql_func.create_tables()
|
||||
print('Database initialized ({})'.format(sql.db.database))
|
||||
|
||||
# Load access tokens and map them to their allowed methods
|
||||
read_actions = util.list_class_methods(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()))
|
||||
|
||||
def run(self):
|
||||
self.socket_server.listen(5)
|
||||
print('Socket is listening')
|
||||
|
||||
while True:
|
||||
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')
|
||||
|
||||
|
||||
def load_config(config_path: str) -> dict:
|
||||
with open(config_path) as config_file:
|
||||
return json.load(config_file)
|
||||
|
||||
|
||||
def main():
|
||||
config = os.path.join(os.path.expanduser('~'), '.config', 'dsst', 'server.json')
|
||||
server = DsstServer(load_config(config))
|
||||
try:
|
||||
server.run()
|
||||
except KeyboardInterrupt:
|
||||
print('Server stopped')
|
||||
server.socket_server.close()
|
||||
try:
|
||||
sys.exit(0)
|
||||
except SystemExit:
|
||||
os._exit(0)
|
||||
5
dsst/dsst_server/tokens.py
Normal file
5
dsst/dsst_server/tokens.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# Define access tokens here
|
||||
# i.E: { 'read': ['a', 'b''],
|
||||
# 'write': ['a']
|
||||
# }
|
||||
TOKENS = [('a', 'rw'), ('b', 'r')]
|
||||
Reference in New Issue
Block a user