SyncTk/classes/frames/settings.py

236 lines
13 KiB
Python
Raw Normal View History

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
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-24 16:27:07 +02:00
self.saves_frame.grid(column=1, row=3, 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)
# ==============
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-24 16:27:07 +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
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-24 16:27:07 +02:00
self.saves_location_button.grid(column=1, row=4, sticky=W, padx=9, pady=9)
2023-01-22 18:53:55 +02:00
# ==============
self.buttons_frame = ThemedFrame(self)
2023-01-24 16:27:07 +02:00
self.buttons_frame.grid(column=0, columnspan=2, row=5, 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-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)
# 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
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\nFor 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
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!")