2023-01-22 18:53:55 +02:00
from os import getenv , path
2023-01-23 01:13:30 +02:00
import platform
2023-01-22 18:53:55 +02:00
from tkinter import N , NSEW , S , W , E , END , IntVar , StringVar , filedialog , messagebox , ttk
from ttkthemes import ThemedTk
from classes . custom . themed_frame import ThemedFrame
from modules . theme_titlebar import theme_title_bar
from modules . utils import configGet , configSet , get_string_mode , use_dark_mode
from modules . logger import logger
2023-01-23 01:13:30 +02:00
from urllib . parse import urlencode , quote
2023-01-22 18:53:55 +02:00
import requests
import sv_ttk
class FrameSettings ( ThemedFrame ) :
def __init__ ( self , master : ThemedTk , * * kwargs ) - > None :
super ( ) . __init__ ( master , * * kwargs )
2023-01-23 01:13:30 +02:00
master . title ( " Settings - Stardew Sync " )
2023-01-22 18:53:55 +02:00
self . columnconfigure ( 0 , weight = 1 )
self . columnconfigure ( 1 , weight = 3 )
master . columnconfigure ( 1 , weight = 1 )
2023-01-23 01:13:30 +02:00
# Name
self . name_label = ttk . Label ( self , text = " Name: " )
self . name_label . grid ( column = 0 , row = 0 , sticky = W , padx = 9 , pady = 9 )
self . name_entry = ttk . Entry ( self )
self . name_entry . grid ( column = 1 , row = 0 , sticky = N + S + E + W , padx = 9 , pady = 9 )
if configGet ( " name " ) is not None :
self . name_entry . insert ( 0 , configGet ( " name " ) )
else :
self . name_entry . insert ( 0 , str ( platform . node ( ) ) )
# ====
2023-01-22 18:53:55 +02:00
# Address
self . address_label = ttk . Label ( self , text = " Address: " )
2023-01-23 01:13:30 +02:00
self . address_label . grid ( column = 0 , row = 1 , sticky = W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
self . address_entry = ttk . Entry ( self )
2023-01-23 01:13:30 +02:00
self . address_entry . grid ( column = 1 , row = 1 , sticky = N + S + E + W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
if configGet ( " address " ) is not None :
self . address_entry . insert ( 0 , configGet ( " address " ) )
# =======
# API Key
self . apikey_label = ttk . Label ( self , text = " API key: " )
2023-01-23 01:13:30 +02:00
self . apikey_label . grid ( column = 0 , row = 2 , sticky = W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
self . apikey_entry = ttk . Entry ( self )
2023-01-23 01:13:30 +02:00
self . apikey_entry . grid ( column = 1 , row = 2 , sticky = N + S + E + W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
if configGet ( " apikey " ) is not None :
self . apikey_entry . insert ( 0 , configGet ( " apikey " ) )
# =======
# Self-signed
if configGet ( " allow_self_signed " ) is not None :
self . self_signed_check_bool = IntVar ( value = int ( configGet ( " allow_self_signed " ) ) )
else :
self . self_signed_check_bool = IntVar ( )
self . self_signed_check = ttk . Checkbutton ( self , text = " Allow self-signed certificates " , variable = self . self_signed_check_bool )
2023-01-23 01:13:30 +02:00
self . self_signed_check . grid ( column = 1 , row = 3 , sticky = W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
# ===========
# Saves location
self . saves_location_label = ttk . Label ( self , text = " Saves location: " )
2023-01-23 01:13:30 +02:00
self . saves_location_label . grid ( column = 0 , row = 4 , sticky = W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
self . saves_frame = ThemedFrame ( self )
2023-01-23 01:13:30 +02:00
self . saves_frame . grid ( column = 1 , row = 4 , sticky = N + S + E + W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
self . saves_frame . grid_columnconfigure ( 0 , weight = 1 )
self . saves_frame . grid_columnconfigure ( 1 , weight = 3 )
2023-01-23 01:13:30 +02:00
self . saves_location_entry = ttk . Entry ( self . saves_frame , width = 30 )
2023-01-22 18:53:55 +02:00
self . saves_location_entry . grid ( column = 0 , row = 0 , sticky = NSEW )
if configGet ( " saves_location " ) is not None :
self . saves_location_entry . insert ( 0 , configGet ( " saves_location " ) )
self . saves_location_button = ttk . Button ( self . saves_frame , text = " Browse " , width = 6 , command = lambda : self . select_location ( self . saves_location_entry ) )
self . saves_location_button . grid ( column = 1 , row = 0 , sticky = E )
# ==============
# Saves location
self . saves_location_label = ttk . Label ( self , text = " Color theme: " )
self . saves_location_label . grid ( column = 0 , row = 5 , sticky = W , padx = 9 , pady = 9 )
if configGet ( " dark_mode_auto " ) is True :
self . default_theme = " Auto "
else :
self . default_theme = " Dark " if configGet ( " dark_mode " ) is True else " Light "
self . chosen_theme = StringVar ( )
self . themes = ( " Auto " , " Light " , " Dark " )
self . saves_location_button = ttk . OptionMenu ( self , self . chosen_theme , self . default_theme , * self . themes , direction = " below " , command = self . change_theme )
self . saves_location_button . grid ( column = 1 , row = 5 , sticky = W , padx = 9 , pady = 9 )
# ==============
self . buttons_frame = ThemedFrame ( self )
2023-01-23 01:13:30 +02:00
self . buttons_frame . grid ( column = 0 , columnspan = 2 , row = 6 , sticky = NSEW , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
self . buttons_frame . grid_columnconfigure ( 0 , weight = 1 )
self . validate_button = ttk . Button ( self . buttons_frame , text = " Validate " , width = 11 , command = self . validate_configuration )
self . validate_button . grid ( column = 0 , row = 0 , sticky = E , padx = 9 )
self . save_button = ttk . Button ( self . buttons_frame , text = " Save " , style = " Accent.TButton " , width = 11 , state = " disabled " , command = self . save_configuration )
self . save_button . grid ( column = 1 , row = 0 , sticky = E )
def change_theme ( self , * args ) :
if self . chosen_theme . get ( ) . strip ( ) . lower ( ) == " auto " :
self . chosen_theme_real = " dark " if use_dark_mode ( no_config = True ) is True else " light "
else :
self . chosen_theme_real = self . chosen_theme . get ( ) . strip ( ) . lower ( )
theme_title_bar ( self . master , self . chosen_theme_real )
def select_location ( self , entry : ttk . Entry ) :
if path . exists ( path . join ( str ( getenv ( " APPDATA " ) ) , " Stardew Valley " ) ) :
self . path_start = path . join ( str ( getenv ( " APPDATA " ) ) , " Stardew Valley " )
elif path . exists ( path . join ( path . expanduser ( " ~ " ) , " .config " , " Stardew Valley " ) ) :
self . path_start = path . join ( path . expanduser ( " ~ " ) , " .config " , " Stardew Valley " )
else :
self . path_start = None
self . path_dir = filedialog . askdirectory ( initialdir = self . path_start , title = " Select Stardew Valley Saves folder " )
if self . path_dir != " " :
entry . delete ( 0 , END )
entry . insert ( 0 , self . path_dir )
def save_configuration ( self ) :
if len ( self . address_entry . get ( ) ) > 0 :
if self . address_entry . get ( ) . endswith ( " / " ) :
self . address_text = self . address_entry . get ( ) [ : - 1 ]
else :
self . address_text = self . address_entry . get ( )
else :
self . address_text = None
2023-01-23 01:13:30 +02:00
# =========================
if self . name_entry . get ( ) . strip ( ) == configGet ( " name " ) :
existing_device = requests . get ( f ' { self . address_entry . get ( ) } /devices/ { quote ( configGet ( " name " ) . encode ( " utf-8 " ) ) } ' , headers = { " apikey " : self . apikey_entry . get ( ) } , verify = not bool ( self . self_signed_check_bool . get ( ) ) )
if existing_device . status_code != 200 :
requests . post ( f ' { self . address_entry . get ( ) } /devices? { urlencode ( { " name " : quote ( configGet ( " name " ) . encode ( " utf-8 " ) ) , " os " : platform . system ( ) + " " + platform . release ( ) , " client " : f " SyncTk { self . master . __version__ } " } ) } ' , headers = { " apikey " : self . apikey_entry . get ( ) } , verify = not bool ( self . self_signed_check_bool . get ( ) ) )
else :
if configGet ( " name " ) is not None :
existing_device_before = requests . get ( f ' { self . address_entry . get ( ) } /devices/ { quote ( configGet ( " name " ) . encode ( " utf-8 " ) ) } ' , headers = { " apikey " : self . apikey_entry . get ( ) } , verify = not bool ( self . self_signed_check_bool . get ( ) ) )
if existing_device_before . status_code == 200 :
requests . patch ( f ' { self . address_entry . get ( ) } /devices/ { quote ( configGet ( " name " ) . encode ( " utf-8 " ) ) } ? { urlencode ( { " new_name " : quote ( self . name_entry . get ( ) . strip ( ) . encode ( " utf-8 " ) ) , " os " : platform . system ( ) + " " + platform . release ( ) , " client " : f " SyncTk { self . master . __version__ } " } ) } ' , headers = { " apikey " : self . apikey_entry . get ( ) } , verify = not bool ( self . self_signed_check_bool . get ( ) ) )
else :
device_created = requests . post ( f ' { self . address_entry . get ( ) } /devices? { urlencode ( { " name " : quote ( self . name_entry . get ( ) . strip ( ) . encode ( " utf-8 " ) ) , " os " : platform . system ( ) + " " + platform . release ( ) , " client " : f " SyncTk { self . master . __version__ } " } ) } ' , headers = { " apikey " : self . apikey_entry . get ( ) } , verify = not bool ( self . self_signed_check_bool . get ( ) ) )
if device_created . status_code != 204 :
messagebox . showerror ( title = " Name error " , message = f " Could not register device in database using name ' { self . name_entry . get ( ) . strip ( ) } ' with error: \n \n { device_created . json ( ) } " )
return
# =========================
configSet ( [ " name " ] , self . name_entry . get ( ) . strip ( ) )
configSet ( [ " address " ] , self . address_entry . get ( ) )
2023-01-22 18:53:55 +02:00
configSet ( [ " apikey " ] , self . apikey_entry . get ( ) )
configSet ( [ " allow_self_signed " ] , bool ( self . self_signed_check_bool . get ( ) ) )
configSet ( [ " saves_location " ] , self . saves_location_entry . get ( ) )
if self . chosen_theme . get ( ) . strip ( ) . lower ( ) == " auto " :
configSet ( [ " dark_mode_auto " ] , True )
else :
configSet ( [ " dark_mode_auto " ] , False )
if self . chosen_theme . get ( ) . strip ( ) . lower ( ) == " dark " :
configSet ( [ " dark_mode " ] , True )
else :
configSet ( [ " dark_mode " ] , False )
# messagebox.showinfo(title="Configuration saved", message="Your client's configuration has been saved")
self . save_button . state ( [ " disabled " ] )
self . master . item_saves . state ( [ " !disabled " ] )
self . master . item_devices . state ( [ " !disabled " ] )
def validate_configuration ( self ) :
2023-01-23 01:13:30 +02:00
if self . name_entry . get ( ) . strip ( ) == " " :
logger . error ( f " Name { self . name_entry . get ( ) . strip ( ) } is not a valid name " )
messagebox . showerror ( title = " Name error " , message = " Provided device name is not valid. Please provide a valid one. " )
return
2023-01-22 18:53:55 +02:00
if len ( self . address_entry . get ( ) ) > 0 :
if self . address_entry . get ( ) . endswith ( " / " ) :
self . address_text = self . address_entry . get ( )
self . address_entry . delete ( 0 , END )
self . address_entry . insert ( 0 , self . address_text [ : - 1 ] )
try :
requests . get ( self . address_entry . get ( ) + " /check " , verify = not bool ( self . self_signed_check_bool . get ( ) ) )
except ( requests . exceptions . InvalidURL , requests . exceptions . InvalidSchema , requests . exceptions . MissingSchema ) as exp :
logger . error ( f " Could not validate ' { self . address_entry . get ( ) } ' due to { exp } " )
messagebox . showerror ( title = " Invalid address " , message = " Address entered does not seem to be correct one. Please provide API address starting with http:// or https:// \n \n For example: \n - https://your-api.com:8043 \n - https://your-api.com " )
return
except ( requests . exceptions . SSLError ) :
logger . error ( f " SSL certificate of ' { self . address_entry . get ( ) } ' does not seem to be valid or is self-signed " )
messagebox . showerror ( title = " Invalid SSL " , message = " SSL certificate seems to be invalid or self-signed and thus won ' t be trusted. You can overwrite this by checking ' Allow self-signed certificates ' . " )
return
except Exception as exp :
logger . error ( f " Could not reach ' { self . address_entry . get ( ) } ' due to { exp } " )
messagebox . showerror ( title = " Connection error " , message = " Address entered does not seem to be reachable. Please make sure you ' ve entered the correct API address. " )
return
response_apikey = requests . get ( self . address_entry . get ( ) + " /apikey " , headers = { " apikey " : self . apikey_entry . get ( ) } , verify = not bool ( self . self_signed_check_bool . get ( ) ) )
if response_apikey . status_code != 200 :
logger . error ( f " API key seems to be invalid. API returned { response_apikey . status_code } as a status code " )
messagebox . showerror ( title = " Invalid apikey " , message = " API key provided does not seem to be valid. Please check for any mistakes and if none found - take a look at the docs to learn how to generate one. " )
return
if not path . exists ( self . saves_location_entry . get ( ) ) :
logger . error ( f " Path { self . saves_location_entry . get ( ) } does not seem to exist " )
messagebox . showerror ( title = " Location error " , message = " Saves folder seems to be invalid. Please provide a valid directory path where Stardew Valley ' s save files (and folders) are stored. " )
return
2023-01-23 01:13:30 +02:00
# =========================
if self . name_entry . get ( ) . strip ( ) != configGet ( " name " ) :
existing_device_after = requests . get ( f ' { self . address_entry . get ( ) } /devices/ { self . name_entry . get ( ) . strip ( ) } ' , headers = { " apikey " : self . apikey_entry . get ( ) } , verify = not bool ( self . self_signed_check_bool . get ( ) ) )
if existing_device_after == 200 :
logger . error ( f " Device with name { self . name_entry . get ( ) . strip ( ) } already exists " )
messagebox . showerror ( title = " Name error " , message = f " Device with name ' { self . name_entry . get ( ) . strip ( ) } ' already exists on the server. Please choose another name or rename that device first. " )
return
# =========================
2023-01-22 18:53:55 +02:00
self . save_button . state ( [ " !disabled " ] )
2023-01-23 01:13:30 +02:00
# messagebox.showinfo(title="Configuration completed", message="Your client is now configured and ready to use!")