added a bunch of settings, updated progress bar to simulate the download prgoress (not as bad as it sounds)

This commit is contained in:
faroukbmiled 2023-08-16 10:05:59 +01:00
parent baa1ee45b1
commit 1c3cdc7cbf

View File

@ -26,12 +26,14 @@ LATEST_RELEASE_URL = "https://github.com/faroukbmiled/BOIIIWD/releases/latest/do
UPDATER_FOLDER = "update" UPDATER_FOLDER = "update"
CONFIG_FILE_PATH = "config.ini" CONFIG_FILE_PATH = "config.ini"
# fuck it we ball, ill remove these when i finish with eveything (and replace them with none global bools) # fuck it we ball, ill get rid of globals when i finish everything cant be bothered rn
global stopped, steampid, console, clean_on_finish global stopped, steampid, console, clean_on_finish, continuous, estimated_progress
steampid = None steampid = None
stopped = False stopped = False
console = False console = False
clean_on_finish = True clean_on_finish = True
continuous = True
estimated_progress = True
ctk.set_appearance_mode("Dark") # Modes: "System" (standard), "Dark", "Light" ctk.set_appearance_mode("Dark") # Modes: "System" (standard), "Dark", "Light"
ctk.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue" ctk.set_default_color_theme("dark-blue") # Themes: "blue" (standard), "green", "dark-blue"
@ -179,12 +181,15 @@ def create_default_config():
with open(CONFIG_FILE_PATH, "w") as config_file: with open(CONFIG_FILE_PATH, "w") as config_file:
config.write(config_file) config.write(config_file)
def run_steamcmd_command(command, self): def run_steamcmd_command(command, self, map_folder):
global steampid, stopped
steamcmd_path = get_steamcmd_path() steamcmd_path = get_steamcmd_path()
show_console = subprocess.CREATE_NO_WINDOW show_console = subprocess.CREATE_NO_WINDOW
if console: if console:
show_console = subprocess.CREATE_NEW_CONSOLE show_console = subprocess.CREATE_NEW_CONSOLE
if continuous:
while not os.path.exists(map_folder) and not stopped:
process = subprocess.Popen( process = subprocess.Popen(
[steamcmd_path + "\steamcmd.exe"] + command.split(), [steamcmd_path + "\steamcmd.exe"] + command.split(),
stdout=None if console else subprocess.PIPE, stdout=None if console else subprocess.PIPE,
@ -195,7 +200,24 @@ def run_steamcmd_command(command, self):
creationflags=show_console creationflags=show_console
) )
global steampid steampid = process.pid
if process.poll() is not None:
return process.returncode
process.communicate()
else:
process = subprocess.Popen(
[steamcmd_path + "\steamcmd.exe"] + command.split(),
stdout=None if console else subprocess.PIPE,
stderr=None if console else subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True,
creationflags=show_console
)
steampid = process.pid steampid = process.pid
if process.poll() is not None: if process.poll() is not None:
@ -203,15 +225,15 @@ def run_steamcmd_command(command, self):
process.communicate() process.communicate()
show_message("SteamCMD has terminated", "SteamCMD has been terminated\nTry again if it randomly stopped!") if not os.path.exists(map_folder):
global stopped show_message("SteamCMD has terminated", "SteamCMD has been terminated\nAnd failed to download the map/mod, try again or enable continuous download in settings")
stopped = True stopped = True
self.button_download.configure(state="normal") self.button_download.configure(state="normal")
self.button_stop.configure(state="disabled") self.button_stop.configure(state="disabled")
return process.returncode return process.returncode
def get_steamcmd_path(): def get_steamcmd_path():
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(CONFIG_FILE_PATH) config.read(CONFIG_FILE_PATH)
@ -237,9 +259,11 @@ def extract_json_data(json_path):
data = json.load(json_file) data = json.load(json_file)
return data["Type"], data["FolderName"] return data["Type"], data["FolderName"]
def convert_bytes_to_readable(size_in_bytes): def convert_bytes_to_readable(size_in_bytes, no_symb=None):
for unit in ['B', 'KB', 'MB', 'GB', 'TB']: for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if size_in_bytes < 1024.0: if size_in_bytes < 1024.0:
if no_symb:
return f"{size_in_bytes:.2f}"
return f"{size_in_bytes:.2f} {unit}" return f"{size_in_bytes:.2f} {unit}"
size_in_bytes /= 1024.0 size_in_bytes /= 1024.0
@ -299,6 +323,11 @@ def remove_tree(folder_path, show_error=None):
except Exception as e: except Exception as e:
pass pass
def convert_seconds(seconds):
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
return hours, minutes, seconds
# End helper functions # End helper functions
class UpdateWindow(ctk.CTkToplevel): class UpdateWindow(ctk.CTkToplevel):
@ -434,14 +463,31 @@ class SettingsTab(ctk.CTkFrame):
self.checkbox_show_console_tooltip = CTkToolTip(self.checkbox_show_console, message="Toggle SteamCMD console\nPlease don't close the Console If you want to stop press the Stop button") self.checkbox_show_console_tooltip = CTkToolTip(self.checkbox_show_console, message="Toggle SteamCMD console\nPlease don't close the Console If you want to stop press the Stop button")
self.console_var.set(self.load_settings("console")) self.console_var.set(self.load_settings("console"))
# Show continuous checkbox
self.continuous_var = ctk.BooleanVar()
self.continuous_var.trace_add("write", self.enable_save_button)
self.checkbox_continuous = ctk.CTkSwitch(left_frame, text="Continuous Download", variable=self.continuous_var)
self.checkbox_continuous.grid(row=2, column=1, padx=20, pady=(20, 0), sticky="nw")
self.checkbox_continuous_tooltip = CTkToolTip(self.checkbox_continuous, message="This will make sure that the download restarts and resumes! until it finishes if steamcmd crashes randomly (it will not redownload from the start)")
self.continuous_var.set(self.load_settings("continuous_download"))
# clean on finish checkbox # clean on finish checkbox
self.clean_checkbox_var = ctk.BooleanVar() self.clean_checkbox_var = ctk.BooleanVar()
self.clean_checkbox_var.trace_add("write", self.enable_save_button) self.clean_checkbox_var.trace_add("write", self.enable_save_button)
self.clean_checkbox = ctk.CTkSwitch(left_frame, text="Clean on finish", variable=self.clean_checkbox_var) self.clean_checkbox = ctk.CTkSwitch(left_frame, text="Clean on finish", variable=self.clean_checkbox_var)
self.clean_checkbox.grid(row=2, column=1, padx=20, pady=(20, 0), sticky="nw") self.clean_checkbox.grid(row=3, column=1, padx=20, pady=(20, 0), sticky="nw")
self.clean_checkbox_tooltip = CTkToolTip(self.clean_checkbox, message="Cleans the map that have been downloaded and installed from steamcmd's steamapps folder ,to save space") self.clean_checkbox_tooltip = CTkToolTip(self.clean_checkbox, message="Cleans the map that have been downloaded and installed from steamcmd's steamapps folder ,to save space")
self.clean_checkbox_var.set(self.load_settings("clean_on_finish", "on")) self.clean_checkbox_var.set(self.load_settings("clean_on_finish", "on"))
# Show estimated_progress checkbox
self.estimated_progress_var = ctk.BooleanVar()
self.estimated_progress_var.trace_add("write", self.enable_save_button)
self.estimated_progress = ctk.CTkSwitch(left_frame, text="Estimated Progress Bar", variable=self.estimated_progress_var)
self.estimated_progress.grid(row=4, column=1, padx=20, pady=(20, 0), sticky="nw")
self.estimated_progress_var_tooltip = CTkToolTip(self.estimated_progress, message="This will change how to progress bar works by estimating how long the download will take\
\nThis is not accurate ,it's better than with it off which is calculating the downloaded folder size which steamcmd dumps the full size rigth mostly")
self.estimated_progress_var.set(self.load_settings("estimated_progress", "on"))
# Check for updates button n Launch boiii # Check for updates button n Launch boiii
self.check_for_updates = ctk.CTkButton(right_frame, text="Check for updates", command=self.settings_check_for_updates) self.check_for_updates = ctk.CTkButton(right_frame, text="Check for updates", command=self.settings_check_for_updates)
self.check_for_updates.grid(row=1, column=1, padx=20, pady=(20, 0), sticky="n") self.check_for_updates.grid(row=1, column=1, padx=20, pady=(20, 0), sticky="n")
@ -466,7 +512,7 @@ class SettingsTab(ctk.CTkFrame):
def save_settings(self): def save_settings(self):
self.save_button.configure(state='disabled') self.save_button.configure(state='disabled')
global console, clean_on_finish global console, clean_on_finish, continuous, estimated_progress
if self.check_updates_checkbox.get(): if self.check_updates_checkbox.get():
save_config("checkforupdtes", "on") save_config("checkforupdtes", "on")
else: else:
@ -486,8 +532,22 @@ class SettingsTab(ctk.CTkFrame):
save_config("clean_on_finish", "off") save_config("clean_on_finish", "off")
clean_on_finish = False clean_on_finish = False
if self.checkbox_continuous.get():
save_config("continuous_download", "on")
continuous = True
else:
save_config("continuous_download", "off")
continuous = False
if self.estimated_progress.get():
save_config("estimated_progress", "on")
estimated_progress = True
else:
save_config("estimated_progress", "off")
estimated_progress = False
def load_settings(self, setting, fallback=None): def load_settings(self, setting, fallback=None):
global console, clean_on_finish global console, clean_on_finish, continuous, estimated_progress
if setting == "console": if setting == "console":
if check_config(setting, fallback) == "on": if check_config(setting, fallback) == "on":
console = True console = True
@ -495,6 +555,15 @@ class SettingsTab(ctk.CTkFrame):
else: else:
console = False console = False
return 0 return 0
if setting == "continuous_download":
if check_config(setting, "on") == "on":
continuous = True
return 1
else:
continuous = False
return 0
if setting == "clean_on_finish": if setting == "clean_on_finish":
if check_config(setting, fallback) == "on": if check_config(setting, fallback) == "on":
clean_on_finish = True clean_on_finish = True
@ -502,6 +571,13 @@ class SettingsTab(ctk.CTkFrame):
else: else:
clean_on_finish = False clean_on_finish = False
return 0 return 0
if setting == "estimated_progress":
if check_config(setting, fallback) == "on":
estimated_progress = True
return 1
else:
estimated_progress = False
return 0
else: else:
if check_config(setting, fallback) == "on": if check_config(setting, fallback) == "on":
return 1 return 1
@ -539,6 +615,7 @@ class BOIIIWD(ctk.CTk):
self.title("BOIII Workshop Downloader - Main") self.title("BOIII Workshop Downloader - Main")
self.geometry(f"{910}x{560}") self.geometry(f"{910}x{560}")
self.wm_iconbitmap('ryuk.ico') self.wm_iconbitmap('ryuk.ico')
self.protocol("WM_DELETE_WINDOW", self.on_closing)
# configure grid layout (4x4) # configure grid layout (4x4)
self.grid_columnconfigure(1, weight=1) self.grid_columnconfigure(1, weight=1)
@ -596,6 +673,9 @@ class BOIIIWD(ctk.CTk):
self.label_speed = ctk.CTkLabel(master=self.slider_progressbar_frame, text="Network Speed: 0 KB/s") self.label_speed = ctk.CTkLabel(master=self.slider_progressbar_frame, text="Network Speed: 0 KB/s")
self.label_speed.grid(row=1, column=0, padx=20, pady=(0, 10), sticky="w") self.label_speed.grid(row=1, column=0, padx=20, pady=(0, 10), sticky="w")
self.elapsed_time = ctk.CTkLabel(master=self.slider_progressbar_frame, text="")
self.elapsed_time.grid(row=1, column=1, padx=20, pady=(0, 10), sticky="nsew", columnspan=1) # Set columnspan to 1
self.label_file_size = ctk.CTkLabel(master=self.slider_progressbar_frame, text="File size: 0KB") self.label_file_size = ctk.CTkLabel(master=self.slider_progressbar_frame, text="File size: 0KB")
self.label_file_size.grid(row=1, column=2, padx=(0, 20), pady=(0, 10), sticky="e") self.label_file_size.grid(row=1, column=2, padx=(0, 20), pady=(0, 10), sticky="e")
@ -654,6 +734,7 @@ class BOIIIWD(ctk.CTk):
self.progress_bar.set(0.0) self.progress_bar.set(0.0)
self.hide_settings_widgets() self.hide_settings_widgets()
self.button_stop.configure(state="disabled") self.button_stop.configure(state="disabled")
self.is_downloading = False
# sidebar windows bouttons # sidebar windows bouttons
self.sidebar_main.configure(command=self.main_button_event, text="Main", fg_color=("#3d3d3d")) self.sidebar_main.configure(command=self.main_button_event, text="Main", fg_color=("#3d3d3d"))
@ -673,17 +754,21 @@ class BOIIIWD(ctk.CTk):
self.deiconify() self.deiconify()
try: try:
global console self.settings_tab.load_settings("clean_on_finish", "on")
if check_config("console") == "on": self.settings_tab.load_settings("continuous_download", "on")
console = True self.settings_tab.load_settings("console", "off")
else: self.settings_tab.load_settings("estimated_progress", "on")
console = False
except: except:
pass pass
if not check_steamcmd(): if not check_steamcmd():
self.show_warning_message() self.show_warning_message()
def on_closing(self):
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
self.stop_download(on_close=True)
os._exit(0)
def id_chnaged_handler(self, some=None, other=None ,shit=None): def id_chnaged_handler(self, some=None, other=None ,shit=None):
self.after(1, self.label_file_size.configure(text=f"File size: 0KB")) self.after(1, self.label_file_size.configure(text=f"File size: 0KB"))
@ -908,6 +993,15 @@ class BOIIIWD(ctk.CTk):
text.pack() text.pack()
def download_map(self): def download_map(self):
if not self.is_downloading:
self.is_downloading = True
start_down_thread = threading.Thread(target=self.download_thread)
start_down_thread.start()
else:
show_message("Warning", "Already downloading a map.")
def download_thread(self):
try:
global stopped global stopped
stopped = False stopped = False
@ -932,6 +1026,7 @@ class BOIIIWD(ctk.CTk):
workshop_id = self.edit_workshop_id.get().strip() workshop_id = self.edit_workshop_id.get().strip()
destination_folder = self.edit_destination_folder.get().strip() destination_folder = self.edit_destination_folder.get().strip()
ws_file_size = get_workshop_file_size(workshop_id)
if not workshop_id.isdigit(): if not workshop_id.isdigit():
try: try:
@ -944,7 +1039,7 @@ class BOIIIWD(ctk.CTk):
show_message("Warning", "Please enter a valid Workshop ID.", icon="warning") show_message("Warning", "Please enter a valid Workshop ID.", icon="warning")
return return
file_size = get_workshop_file_size(workshop_id) file_size = ws_file_size
if not valid_id(workshop_id): if not valid_id(workshop_id):
show_message("Warning", "Please enter a valid Workshop ID.", icon="warning") show_message("Warning", "Please enter a valid Workshop ID.", icon="warning")
@ -969,8 +1064,13 @@ class BOIIIWD(ctk.CTk):
os.makedirs(download_folder) os.makedirs(download_folder)
def check_and_update_progress(): def check_and_update_progress():
# delay untill steam boots up and starts downloading (ive a better idea ill implement it later)
time.sleep(8)
global stopped global stopped
previous_net_speed = 0 previous_net_speed = 0
est_downloaded_bytes = 0
start_time = time.time()
file_size = ws_file_size
while not stopped: while not stopped:
try: try:
@ -979,6 +1079,46 @@ class BOIIIWD(ctk.CTk):
current_size = sum(os.path.getsize(os.path.join(map_folder, f)) for f in os.listdir(map_folder)) current_size = sum(os.path.getsize(os.path.join(map_folder, f)) for f in os.listdir(map_folder))
progress = int(current_size / file_size * 100) progress = int(current_size / file_size * 100)
if progress > 100:
progress = int(current_size / current_size * 100)
file_size = current_size
self.after(1, lambda p=progress: self.label_file_size.configure(text=f"Wrong size reported\nActual size: ~{convert_bytes_to_readable(current_size)}"))
if estimated_progress:
time_elapsed = time.time() - start_time
raw_net_speed = psutil.net_io_counters().bytes_recv
current_net_speed_text = raw_net_speed
net_speed_bytes = current_net_speed_text - previous_net_speed
previous_net_speed = current_net_speed_text
current_net_speed = net_speed_bytes
down_cap = 150000000
if current_net_speed >= down_cap:
current_net_speed = 264029
est_downloaded_bytes += current_net_speed
percentage_complete = (est_downloaded_bytes / file_size) * 100
progress = min(percentage_complete / 100, 0.99)
net_speed, speed_unit = convert_speed(net_speed_bytes)
elapsed_hours, elapsed_minutes, elapsed_seconds = convert_seconds(time_elapsed)
print(f"raw_net {raw_net_speed}\ncurrent_net_speed: {current_net_speed}\nest_downloaded_bytes {est_downloaded_bytes}\npercentage_complete {percentage_complete}\nprogress {progress}")
self.after(1, self.progress_bar.set(progress))
self.after(1, lambda v=net_speed: self.label_speed.configure(text=f"Network Speed: {v:.2f} {speed_unit}"))
self.after(1, lambda p=percentage_complete: self.progress_text.configure(text=f"{p:.2f}%"))
self.after(1, lambda h=elapsed_hours, m=elapsed_minutes, s=elapsed_seconds: self.elapsed_time.configure(text=f"Elapsed Time: {int(h):02d}:{int(m):02d}:{int(s):02d}"))
time.sleep(1)
else:
time_elapsed = time.time() - start_time
progress = int(current_size / file_size * 100)
self.after(1, lambda v=progress / 100.0: self.progress_bar.set(v)) self.after(1, lambda v=progress / 100.0: self.progress_bar.set(v))
current_net_speed = psutil.net_io_counters().bytes_recv current_net_speed = psutil.net_io_counters().bytes_recv
@ -987,13 +1127,15 @@ class BOIIIWD(ctk.CTk):
previous_net_speed = current_net_speed previous_net_speed = current_net_speed
net_speed, speed_unit = convert_speed(net_speed_bytes) net_speed, speed_unit = convert_speed(net_speed_bytes)
elapsed_hours, elapsed_minutes, elapsed_seconds = convert_seconds(time_elapsed)
self.after(1, lambda v=net_speed: self.label_speed.configure(text=f"Network Speed: {v:.2f} {speed_unit}")) self.after(1, lambda v=net_speed: self.label_speed.configure(text=f"Network Speed: {v:.2f} {speed_unit}"))
self.after(1, lambda p=progress: self.progress_text.configure(text=f"{p}%")) self.after(1, lambda p=progress: self.progress_text.configure(text=f"{p}%"))
self.after(1, lambda h=elapsed_hours, m=elapsed_minutes, s=elapsed_seconds: self.elapsed_time.configure(text=f"Elapsed Time: {int(h):02d}:{int(m):02d}:{int(s):02d}"))
time.sleep(1) time.sleep(1)
command = f"+login anonymous +workshop_download_item 311210 {workshop_id} +quit" command = f"+login anonymous +@sSteamCmdForcePlatformBitness 64 +app_info_update 1 +app_info_print 311210 app_update 311210 +workshop_download_item 311210 {workshop_id} validate +quit"
steamcmd_thread = threading.Thread(target=lambda: run_steamcmd_command(command, self)) steamcmd_thread = threading.Thread(target=lambda: run_steamcmd_command(command, self, map_folder))
steamcmd_thread.start() steamcmd_thread.start()
def wait_for_threads(): def wait_for_threads():
@ -1053,10 +1195,19 @@ class BOIIIWD(ctk.CTk):
self.button_download.configure(state="disabled") self.button_download.configure(state="disabled")
self.button_stop.configure(state="normal") self.button_stop.configure(state="normal")
def stop_download(self): finally:
self.stop_download
self.is_downloading = False
def stop_download(self, on_close=None):
global stopped global stopped
stopped = True stopped = True
if on_close:
subprocess.run(['taskkill', '/F', '/IM', 'steamcmd.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
creationflags=subprocess.CREATE_NO_WINDOW)
return
subprocess.run(['taskkill', '/F', '/IM', 'steamcmd.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, subprocess.run(['taskkill', '/F', '/IM', 'steamcmd.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
creationflags=subprocess.CREATE_NO_WINDOW) creationflags=subprocess.CREATE_NO_WINDOW)
@ -1064,6 +1215,7 @@ class BOIIIWD(ctk.CTk):
self.button_stop.configure(state="disabled") self.button_stop.configure(state="disabled")
self.label_speed.configure(text="Network Speed: 0 KB/s") self.label_speed.configure(text="Network Speed: 0 KB/s")
self.progress_text.configure(text="0%") self.progress_text.configure(text="0%")
self.elapsed_time.configure(text=f"")
self.progress_bar.set(0.0) self.progress_bar.set(0.0)
if __name__ == "__main__": if __name__ == "__main__":