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
2023-01-26 14:29:30 +02:00
from classes . enums import SavesPreference , SavesPreferenceButton , Theme
2023-01-22 18:53:55 +02:00
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
2023-01-24 16:27:07 +02:00
# 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-23 01:13:30 +02:00
# ====
2023-01-22 18:53:55 +02:00
# Address
self . address_label = ttk . Label ( self , text = " Address: " )
2023-01-24 16:27:07 +02:00
self . address_label . grid ( column = 0 , row = 0 , sticky = W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
self . address_entry = ttk . Entry ( self )
2023-01-24 16:27:07 +02:00
self . address_entry . grid ( column = 1 , row = 0 , 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-24 16:27:07 +02:00
self . apikey_label . grid ( column = 0 , row = 1 , sticky = W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
self . apikey_entry = ttk . Entry ( self )
2023-01-24 16:27:07 +02:00
self . apikey_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 ( " 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-24 16:27:07 +02:00
self . self_signed_check . grid ( column = 1 , row = 2 , 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-24 16:27:07 +02:00
self . saves_location_label . grid ( column = 0 , row = 3 , sticky = W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
self . saves_frame = ThemedFrame ( self )
2023-01-26 12:10:25 +02:00
self . saves_frame . grid ( column = 1 , row = 3 , sticky = NSEW , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
self . saves_frame . grid_columnconfigure ( 0 , weight = 1 )
2023-01-26 12:10:25 +02:00
# self.saves_frame.grid_columnconfigure(1, weight=3)
2023-01-22 18:53:55 +02:00
2023-01-26 12:10:25 +02:00
self . saves_location_entry = ttk . Entry ( self . saves_frame ) #, width=30)
self . saves_location_entry . grid ( column = 0 , row = 0 , sticky = N + S + E + W )
2023-01-22 18:53:55 +02:00
if configGet ( " saves_location " ) is not None :
self . saves_location_entry . insert ( 0 , configGet ( " saves_location " ) )
2023-01-26 12:10:25 +02:00
self . saves_location_divider = ttk . Separator ( self . saves_frame , orient = " vertical " )
self . saves_location_divider . grid ( column = 1 , row = 0 , padx = 3 )
2023-01-22 18:53:55 +02:00
self . saves_location_button = ttk . Button ( self . saves_frame , text = " Browse " , width = 6 , command = lambda : self . select_location ( self . saves_location_entry ) )
2023-01-26 12:10:25 +02:00
self . saves_location_button . grid ( column = 2 , row = 0 , sticky = E )
2023-01-22 18:53:55 +02:00
# ==============
2023-01-26 12:10:25 +02:00
# Saves preference
self . saves_preference_label = ttk . Label ( self , text = " Saves preference: " )
self . saves_preference_label . grid ( column = 0 , row = 4 , sticky = W , padx = 9 , pady = 9 )
2023-01-26 14:29:30 +02:00
self . default_preference = SavesPreferenceButton . LATEST_UPLOAD . value if configGet ( " prefer_saves " ) == SavesPreference . LATEST_UPLOAD . value else SavesPreferenceButton . LATEST_PROGRESS . value
2023-01-26 12:10:25 +02:00
self . chosen_preference = StringVar ( )
2023-01-26 14:29:30 +02:00
self . preferences = ( SavesPreferenceButton . LATEST_UPLOAD . value , SavesPreferenceButton . LATEST_PROGRESS . value )
2023-01-26 12:10:25 +02:00
self . saves_preference_button = ttk . OptionMenu ( self , self . chosen_preference , self . default_preference , * self . preferences , direction = " below " )
self . saves_preference_button . grid ( column = 1 , row = 4 , sticky = W , padx = 9 , pady = 9 )
# ================
2023-01-24 16:27:07 +02:00
# Software theme
2023-01-22 18:53:55 +02:00
self . saves_location_label = ttk . Label ( self , text = " Color theme: " )
2023-01-26 12:10:25 +02:00
self . saves_location_label . grid ( column = 0 , row = 5 , sticky = W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
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 )
2023-01-26 12:10:25 +02:00
self . saves_location_button . grid ( column = 1 , row = 5 , sticky = W , padx = 9 , pady = 9 )
2023-01-22 18:53:55 +02:00
# ==============
self . buttons_frame = ThemedFrame ( self )
2023-01-26 12:10:25 +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 )
2023-01-26 12:10:25 +02:00
# 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)
2023-01-22 18:53:55 +02:00
2023-01-26 12:10:25 +02:00
self . save_button = ttk . Button ( self . buttons_frame , text = " Save " , style = " Accent.TButton " , width = 11 , command = self . validate_configuration )
2023-01-22 18:53:55 +02:00
self . save_button . grid ( column = 1 , row = 0 , sticky = E )
def change_theme ( self , * args ) :
2023-01-26 14:29:30 +02:00
if self . chosen_theme . get ( ) . strip ( ) . lower ( ) == Theme . AUTO . value :
self . chosen_theme_real = Theme . DARK if use_dark_mode ( no_config = True ) is True else Theme . LIGHT
2023-01-22 18:53:55 +02:00
else :
2023-01-26 14:29:30 +02:00
if self . chosen_theme . get ( ) . strip ( ) . lower ( ) == Theme . LIGHT . value :
self . chosen_theme_real = Theme . LIGHT
else :
self . chosen_theme_real = Theme . DARK
2023-01-22 18:53:55 +02:00
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-24 16:27:07 +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
# # =========================
2023-01-23 01:13:30 +02:00
2023-01-24 16:27:07 +02:00
# configSet(["name"], self.name_entry.get().strip())
2023-01-23 01:13:30 +02:00
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 )
2023-01-26 12:10:25 +02:00
configSet ( [ " prefer_saves " ] , self . chosen_preference . get ( ) . strip ( ) . lower ( ) )
messagebox . showinfo ( title = " Configuration saved " , message = " Your client ' s configuration has been saved " )
2023-01-22 18:53:55 +02:00
2023-01-26 12:10:25 +02:00
# self.save_button.state(["disabled"])
2023-01-22 18:53:55 +02:00
self . master . item_saves . state ( [ " !disabled " ] )
self . master . item_devices . state ( [ " !disabled " ] )
def validate_configuration ( self ) :
2023-01-23 01:13:30 +02:00
2023-01-24 16:27:07 +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-24 16:27:07 +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
2023-01-26 12:10:25 +02:00
# self.save_button.state(["!disabled"])
self . save_configuration ( )
2023-01-22 18:53:55 +02:00
2023-01-23 01:13:30 +02:00
# messagebox.showinfo(title="Configuration completed", message="Your client is now configured and ready to use!")