v0.2.4 preview

This commit is contained in:
faroukbmiled 2023-08-29 20:02:57 +01:00
parent 10ac2564ea
commit 2af9cd8ddb
2 changed files with 312 additions and 183 deletions

View File

@ -20,28 +20,13 @@ import io
import os import os
import re import re
VERSION = "v0.2.3" VERSION = "v0.2.4"
GITHUB_REPO = "faroukbmiled/BOIIIWD" GITHUB_REPO = "faroukbmiled/BOIIIWD"
LATEST_RELEASE_URL = "https://github.com/faroukbmiled/BOIIIWD/releases/latest/download/Release.zip" LATEST_RELEASE_URL = "https://github.com/faroukbmiled/BOIIIWD/releases/latest/download/Release.zip"
UPDATER_FOLDER = "update" UPDATER_FOLDER = "update"
CONFIG_FILE_PATH = "config.ini" CONFIG_FILE_PATH = "config.ini"
RESOURCES_DIR = os.path.join(os.path.dirname(__file__), 'resources') RESOURCES_DIR = os.path.join(os.path.dirname(__file__), 'resources')
# fuck it we ball, ill get rid of globals when i finish everything cant be bothered rn
global stopped, steampid, console, clean_on_finish, continuous, estimated_progress, steam_fail_counter, \
steam_fail_counter_toggle, steam_fail_number, steamcmd_reset, show_fails
steampid = None
stopped = False
console = False
clean_on_finish = True
continuous = True
estimated_progress = True
steam_fail_counter_toggle = False
steam_fail_counter = 0
steam_fail_number = 10
steamcmd_reset = False
show_fails = True
# Start Helper Functions # Start Helper Functions
def cwd(): def cwd():
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
@ -73,6 +58,7 @@ def check_custom_theme(theme_name):
except: except:
return os.path.join(RESOURCES_DIR, "boiiiwd_theme.json") return os.path.join(RESOURCES_DIR, "boiiiwd_theme.json")
# theme initialization
ctk.set_appearance_mode(check_config("appearance", "Dark")) # Modes: "System" (standard), "Dark", "Light" ctk.set_appearance_mode(check_config("appearance", "Dark")) # Modes: "System" (standard), "Dark", "Light"
try: try:
ctk.set_default_color_theme(check_custom_theme(check_config("theme", fallback="boiiiwd_theme.json"))) ctk.set_default_color_theme(check_custom_theme(check_config("theme", fallback="boiiiwd_theme.json")))
@ -218,84 +204,6 @@ 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, map_folder, queue=None):
global steampid, stopped, steam_fail_counter, steam_fail_number, steamcmd_reset
steamcmd_path = get_steamcmd_path()
show_console = subprocess.CREATE_NO_WINDOW
if console:
show_console = subprocess.CREATE_NEW_CONSOLE
if os.path.exists(map_folder):
try:
try:
os.remove(map_folder)
except:
timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
os.rename(map_folder, os.path.join(map_folder, os.path.join(get_steamcmd_path(), "steamapps", "workshop", "content", "311210", f"couldntremove_{timestamp}")))
except Exception as e:
stopped = True
self.queue_stop_button = True
show_message("Error", f"Couldn't remove {map_folder}, please do so manually\n{e}", icon="cancel")
return
if continuous:
while not os.path.exists(map_folder) and not stopped:
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
if process.poll() is not None:
return process.returncode
process.communicate()
steam_fail_counter = steam_fail_counter + 1
if steam_fail_counter_toggle:
# print(steam_fail_counter)
try:
if steam_fail_counter >= int(steam_fail_number):
reset_steamcmd(no_warn=True)
steamcmd_reset = True
steam_fail_counter = 0
except:
if steam_fail_counter >= 10:
reset_steamcmd(no_warn=True)
steam_fail_counter = 0
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
if process.poll() is not None:
return process.returncode
process.communicate()
if not os.path.exists(map_folder):
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
if not queue:
self.button_download.configure(state="normal")
self.button_stop.configure(state="disabled")
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)
@ -353,9 +261,15 @@ def show_message(title, message, icon="warning", exit_on_close=False):
msg = CTkMessagebox(title=title, message=message, icon=icon) msg = CTkMessagebox(title=title, message=message, icon=icon)
def launch_boiii_func(path): def launch_boiii_func(path):
procname = "boiii.exe"
try: try:
boiii_path = os.path.join(path, "boiii.exe") if procname in (p.name() for p in psutil.process_iter()):
subprocess.Popen([boiii_path], cwd=path) for proc in psutil.process_iter():
if proc.name() == procname:
proc.kill()
boiii_path = os.path.join(path, procname)
subprocess.Popen([boiii_path ,"-launch"] , cwd=path)
show_message("Please wait!", "The game has launched in the background it will open up in a sec!", icon="info")
except Exception as e: except Exception as e:
show_message("Error: Failed to launch BOIII", f"Failed to launch boiii.exe\nMake sure to put in your correct boiii path\n{e}") show_message("Error: Failed to launch BOIII", f"Failed to launch boiii.exe\nMake sure to put in your correct boiii path\n{e}")
@ -639,6 +553,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
button_view_list.destroy() button_view_list.destroy()
self.label_list.remove(label) self.label_list.remove(label)
self.button_list.remove(button) self.button_list.remove(button)
self.added_items.remove(label.cget("text"))
self.button_view_list.remove(button_view_list) self.button_view_list.remove(button_view_list)
def refresh_items(self): def refresh_items(self):
@ -829,6 +744,18 @@ class LibraryTab(ctk.CTkScrollableFrame):
class SettingsTab(ctk.CTkFrame): class SettingsTab(ctk.CTkFrame):
def __init__(self, master=None): def __init__(self, master=None):
super().__init__(master) super().__init__(master)
# settings default bools
self.skip_already_installed = True
self.stopped = False
self.console = False
self.clean_on_finish = True
self.continuous = True
self.estimated_progress = True
self.steam_fail_counter_toggle = False
self.steam_fail_counter = 0
self.steam_fail_number = 10
self.steamcmd_reset = False
self.show_fails = True
# Left and right frames, use fg_color="transparent" # Left and right frames, use fg_color="transparent"
self.grid_rowconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1)
@ -876,27 +803,35 @@ class SettingsTab(ctk.CTkFrame):
# Show estimated_progress checkbox # Show estimated_progress checkbox
self.estimated_progress_var = ctk.BooleanVar() self.estimated_progress_var = ctk.BooleanVar()
self.estimated_progress_var.trace_add("write", self.enable_save_button) 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_cb = 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_cb.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\ self.estimated_progress_var_tooltip = CTkToolTip(self.estimated_progress_cb, 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") \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")) self.estimated_progress_var.set(self.load_settings("estimated_progress", "on"))
# Show estimated_progress checkbox # Show show fails checkbox
self.show_fails_var = ctk.BooleanVar() self.show_fails_var = ctk.BooleanVar()
self.show_fails_var.trace_add("write", self.enable_save_button) self.show_fails_var.trace_add("write", self.enable_save_button)
self.show_fails = ctk.CTkSwitch(left_frame, text="Show fails (on top of progress bar):", variable=self.show_fails_var) self.show_fails_cb = ctk.CTkSwitch(left_frame, text="Show fails (on top of progress bar):", variable=self.show_fails_var)
self.show_fails.grid(row=5, column=1, padx=20, pady=(20, 0), sticky="nw") self.show_fails_cb.grid(row=5, column=1, padx=20, pady=(20, 0), sticky="nw")
self.show_fails_tooltip = CTkToolTip(self.show_fails, message="Display how many times steamcmd has failed/crashed\nIf the number is getting high quickly then try pressing Reset SteamCMD and try again, otherwise its fine") self.show_fails_tooltip = CTkToolTip(self.show_fails_cb, message="Display how many times steamcmd has failed/crashed\nIf the number is getting high quickly then try pressing Reset SteamCMD and try again, otherwise its fine")
self.estimated_progress_var.set(self.load_settings("show_fails", "on")) self.estimated_progress_var.set(self.load_settings("show_fails", "on"))
# Show skip_already_installed maps checkbox
self.skip_already_installed_var = ctk.BooleanVar()
self.skip_already_installed_var.trace_add("write", self.enable_save_button)
self.skip_already_installed_ch = ctk.CTkSwitch(left_frame, text="Skip already installed maps:", variable=self.skip_already_installed_var)
self.skip_already_installed_ch.grid(row=6, column=1, padx=20, pady=(20, 0), sticky="nw")
self.skip_already_installed_ch_tooltip = CTkToolTip(self.skip_already_installed_ch, message="If on it will not download installed maps,\nthis can miss sometimes if you remove maps manually and not from library tab while the app is running")
self.skip_already_installed_var.set(self.load_settings("skip_already_installed", "on"))
# Resetr steam on many fails # Resetr steam on many fails
self.reset_steamcmd_on_fail_var = ctk.IntVar() self.reset_steamcmd_on_fail_var = ctk.IntVar()
self.reset_steamcmd_on_fail_var.trace_add("write", self.enable_save_button) self.reset_steamcmd_on_fail_var.trace_add("write", self.enable_save_button)
self.reset_steamcmd_on_fail_text = ctk.CTkLabel(left_frame, text=f"Reset steamcmd on % fails: (n of fails)", anchor="w") self.reset_steamcmd_on_fail_text = ctk.CTkLabel(left_frame, text=f"Reset steamcmd on % fails: (n of fails)", anchor="w")
self.reset_steamcmd_on_fail_text.grid(row=6, column=1, padx=20, pady=(10, 0), sticky="nw") self.reset_steamcmd_on_fail_text.grid(row=7, column=1, padx=20, pady=(10, 0), sticky="nw")
self.reset_steamcmd_on_fail = ctk.CTkOptionMenu(left_frame, values=["20", "30", "40", "Custom", "Disable"], variable=self.reset_steamcmd_on_fail_var, command=self.reset_steamcmd_on_fail_func) self.reset_steamcmd_on_fail = ctk.CTkOptionMenu(left_frame, values=["20", "30", "40", "Custom", "Disable"], variable=self.reset_steamcmd_on_fail_var, command=self.reset_steamcmd_on_fail_func)
self.reset_steamcmd_on_fail.grid(row=7, column=1, padx=20, pady=(0, 0), sticky="nw") self.reset_steamcmd_on_fail.grid(row=8, column=1, padx=20, pady=(0, 0), sticky="nw")
self.reset_steamcmd_on_fail_tooltip = CTkToolTip(self.reset_steamcmd_on_fail, message="This actually fixes steamcmd when its crashing way too much") self.reset_steamcmd_on_fail_tooltip = CTkToolTip(self.reset_steamcmd_on_fail, message="This actually fixes steamcmd when its crashing way too much")
self.reset_steamcmd_on_fail.set(value=self.load_settings("reset_on_fail", "Disable")) self.reset_steamcmd_on_fail.set(value=self.load_settings("reset_on_fail", "Disable"))
@ -976,7 +911,6 @@ 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, continuous, estimated_progress, steam_fail_number, steam_fail_counter_toggle, show_fails
if self.check_updates_checkbox.get(): if self.check_updates_checkbox.get():
save_config("checkforupdtes", "on") save_config("checkforupdtes", "on")
else: else:
@ -984,104 +918,118 @@ class SettingsTab(ctk.CTkFrame):
if self.checkbox_show_console.get(): if self.checkbox_show_console.get():
save_config("console", "on") save_config("console", "on")
console = True self.console = True
else: else:
save_config("console", "off") save_config("console", "off")
console = False self.console = False
if self.skip_already_installed_ch.get():
save_config("skip_already_installed", "on")
self.skip_already_installed = True
else:
save_config("skip_already_installed", "off")
self.skip_already_installed = False
if self.clean_checkbox.get(): if self.clean_checkbox.get():
save_config("clean_on_finish", "on") save_config("clean_on_finish", "on")
clean_on_finish = True self.clean_on_finish = True
else: else:
save_config("clean_on_finish", "off") save_config("clean_on_finish", "off")
clean_on_finish = False self.clean_on_finish = False
if self.checkbox_continuous.get(): if self.checkbox_continuous.get():
save_config("continuous_download", "on") save_config("continuous_download", "on")
continuous = True self.continuous = True
else: else:
save_config("continuous_download", "off") save_config("continuous_download", "off")
continuous = False self.continuous = False
if self.estimated_progress.get(): if self.estimated_progress_cb.get():
save_config("estimated_progress", "on") save_config("estimated_progress", "on")
estimated_progress = True self.estimated_progress = True
else: else:
save_config("estimated_progress", "off") save_config("estimated_progress", "off")
estimated_progress = False self.estimated_progress = False
if self.show_fails.get(): if self.show_fails_cb.get():
save_config("show_fails", "on") save_config("show_fails", "on")
show_fails = True self.show_fails = True
else: else:
save_config("show_fails", "off") save_config("show_fails", "off")
show_fails = False self.show_fails = False
if self.reset_steamcmd_on_fail.get(): if self.reset_steamcmd_on_fail.get():
value = self.reset_steamcmd_on_fail.get() value = self.reset_steamcmd_on_fail.get()
if value == "Disable": if value == "Disable":
steam_fail_counter_toggle = False self.steam_fail_counter_toggle = False
else: else:
steam_fail_counter_toggle = True self.steam_fail_counter_toggle = True
steam_fail_number = int(value) self.steam_fail_number = int(value)
save_config("reset_on_fail", value) save_config("reset_on_fail", value)
def load_settings(self, setting, fallback=None): def load_settings(self, setting, fallback=None):
global console, clean_on_finish, continuous, estimated_progress, steam_fail_counter_toggle, steam_fail_number, show_fails
if setting == "console": if setting == "console":
if check_config(setting, fallback) == "on": if check_config(setting, fallback) == "on":
console = True self.console = True
return 1 return 1
else: else:
console = False self.console = False
return 0 return 0
if setting == "continuous_download": if setting == "continuous_download":
if check_config(setting, "on") == "on": if check_config(setting, "on") == "on":
continuous = True self.continuous = True
return 1 return 1
else: else:
continuous = False self.continuous = False
return 0 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 self.clean_on_finish = True
return 1 return 1
else: else:
clean_on_finish = False self.clean_on_finish = False
return 0 return 0
if setting == "estimated_progress": if setting == "estimated_progress":
if check_config(setting, fallback) == "on": if check_config(setting, fallback) == "on":
estimated_progress = True self.estimated_progress = True
return 1 return 1
else: else:
estimated_progress = False self.estimated_progress = False
return 0 return 0
if setting == "reset_on_fail": if setting == "reset_on_fail":
option = check_config(setting, fallback) option = check_config(setting, fallback)
if option == "Disable" or option == "Custom": if option == "Disable" or option == "Custom":
steam_fail_counter_toggle = False self.steam_fail_counter_toggle = False
return "Disable" return "Disable"
else: else:
try: try:
steam_fail_number = int(option) self.steam_fail_number = int(option)
return option return option
except: except:
if steam_fail_counter_toggle: if self.steam_fail_counter_toggle:
steam_fail_number = 10 self.steam_fail_number = 10
return "10" return "10"
else: else:
steam_fail_number = 10 self.steam_fail_number = 10
return "Disable" return "Disable"
if setting == "show_fails": if setting == "show_fails":
if check_config(setting, fallback) == "on": if check_config(setting, fallback) == "on":
show_fails = True self.show_fails = True
return 1 return 1
else: else:
show_fails = False self.show_fails = False
return 0
if setting == "skip_already_installed":
if check_config(setting, fallback) == "on":
self.skip_already_installed = True
return 1
else:
self.skip_already_installed = False
return 0 return 0
if setting == "theme": if setting == "theme":
@ -1099,6 +1047,8 @@ class SettingsTab(ctk.CTkFrame):
else: else:
return 0 return 0
def boiiiwd_custom_theme(self, disable_only=None): def boiiiwd_custom_theme(self, disable_only=None):
file_to_rename = os.path.join(cwd(), "boiiiwd_theme.json") file_to_rename = os.path.join(cwd(), "boiiiwd_theme.json")
if os.path.exists(file_to_rename): if os.path.exists(file_to_rename):
@ -1128,6 +1078,7 @@ class SettingsTab(ctk.CTkFrame):
self.clean_checkbox_var.set(self.load_settings("clean_on_finish", "on")) self.clean_checkbox_var.set(self.load_settings("clean_on_finish", "on"))
self.continuous_var.set(self.load_settings("continuous_download")) self.continuous_var.set(self.load_settings("continuous_download"))
self.show_fails_var.set(self.load_settings("show_fails", "on")) self.show_fails_var.set(self.load_settings("show_fails", "on"))
self.skip_already_installed_var.set(self.load_settings("skip_already_installed", "on"))
# keep last cuz of trace_add() # keep last cuz of trace_add()
self.save_button.configure(state='disabled') self.save_button.configure(state='disabled')
@ -1309,6 +1260,7 @@ class BOIIIWD(ctk.CTk):
self.is_pressed = False self.is_pressed = False
self.queue_enabled = False self.queue_enabled = False
self.queue_stop_button = False self.queue_stop_button = False
self.is_downloading = False
# sidebar windows bouttons # sidebar windows bouttons
self.sidebar_main.configure(command=self.main_button_event, text="Main ⬇️", fg_color=(self.active_color), state="active") self.sidebar_main.configure(command=self.main_button_event, text="Main ⬇️", fg_color=(self.active_color), state="active")
@ -1337,6 +1289,7 @@ class BOIIIWD(ctk.CTk):
self.settings_tab.load_settings("estimated_progress", "on") self.settings_tab.load_settings("estimated_progress", "on")
self.settings_tab.load_settings("reset_on_fail", "Disable") self.settings_tab.load_settings("reset_on_fail", "Disable")
self.settings_tab.load_settings("show_fails", "on") self.settings_tab.load_settings("show_fails", "on")
self.settings_tab.load_settings("skip_already_installed", "on")
except: except:
pass pass
@ -1473,7 +1426,7 @@ class BOIIIWD(ctk.CTk):
create_default_config() create_default_config()
def help_queue_text_func(self): def help_queue_text_func(self):
if any(char.isalpha() for char in self.queuetextarea.get("1.0", "end")): if any(char.strip() for char in self.queuetextarea.get("1.0", "end")):
self.workshop_queue_label.configure(text="Workshop IDs/Links => press help to see examples:") self.workshop_queue_label.configure(text="Workshop IDs/Links => press help to see examples:")
self.queuetextarea.configure(state="normal") self.queuetextarea.configure(state="normal")
self.queuetextarea.delete(1.0, "end") self.queuetextarea.delete(1.0, "end")
@ -1702,9 +1655,148 @@ class BOIIIWD(ctk.CTk):
top.grid_columnconfigure(0, weight=1) top.grid_columnconfigure(0, weight=1)
top.grid_columnconfigure(1, weight=1) top.grid_columnconfigure(1, weight=1)
def check_steamcmd_stdout(self, log_file_path, target_item_id):
temp_file_path = log_file_path + '.temp'
shutil.copy2(log_file_path, temp_file_path)
try:
with open(temp_file_path, 'r') as log_file:
log_file.seek(0, os.SEEK_END)
file_size = log_file.tell()
position = file_size
lines_found = 0
while lines_found < 7 and position > 0:
position -= 1
log_file.seek(position, os.SEEK_SET)
char = log_file.read(1)
if char == '\n':
lines_found += 1
lines = log_file.readlines()[-7:]
for line in reversed(lines):
line = line.lower().strip()
if f"download item {target_item_id.strip()}" in line:
return True
return False
finally:
os.remove(temp_file_path)
# the real deal
def run_steamcmd_command(self, command, map_folder, wsid, queue=None):
steamcmd_path = get_steamcmd_path()
stdout = os.path.join(steamcmd_path, "logs", "workshop_log.txt")
timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
try:
with open(stdout, 'w') as file:
file.write('')
except:
os.rename(stdout, os.path.join(map_folder, os.path.join(stdout, f"workshop_log_couldntremove_{timestamp}.txt")))
show_console = subprocess.CREATE_NO_WINDOW
if self.settings_tab.console:
show_console = subprocess.CREATE_NEW_CONSOLE
if os.path.exists(map_folder):
try:
try:
os.remove(map_folder)
except:
os.rename(map_folder, os.path.join(map_folder, os.path.join(get_steamcmd_path(), "steamapps", "workshop", "content", "311210", f"couldntremove_{timestamp}")))
except Exception as e:
self.settings_tab.stopped = True
self.queue_stop_button = True
show_message("Error", f"Couldn't remove {map_folder}, please do so manually\n{e}", icon="cancel")
self.stop_download
return
if self.settings_tab.continuous:
while not os.path.exists(map_folder) and not self.settings_tab.stopped:
process = subprocess.Popen(
[steamcmd_path + "\steamcmd.exe"] + command.split(),
stdout=None if self.settings_tab.console else subprocess.PIPE,
stderr=None if self.settings_tab.console else subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True,
creationflags=show_console
)
#wait for process
while True:
if not self.is_downloading:
if self.check_steamcmd_stdout(stdout, wsid):
self.is_downloading = True
if process.poll() != None:
break
time.sleep(1)
# print("Broken freeeee!")
self.is_downloading = False
try:
with open(stdout, 'w') as file:
file.write('')
except:
os.rename(stdout, os.path.join(map_folder, os.path.join(stdout, f"workshop_log_couldntremove_{timestamp}.txt")))
self.settings_tab.steam_fail_counter = self.settings_tab.steam_fail_counter + 1
if self.settings_tab.steam_fail_counter_toggle:
try:
if self.settings_tab.steam_fail_counter >= int(self.settings_tab.steam_fail_number):
reset_steamcmd(no_warn=True)
self.settings_tab.steamcmd_reset = True
self.settings_tab.steam_fail_counter = 0
except:
if self.settings_tab.steam_fail_counter >= 10:
reset_steamcmd(no_warn=True)
self.settings_tab.steam_fail_counter = 0
else:
process = subprocess.Popen(
[steamcmd_path + "\steamcmd.exe"] + command.split(),
stdout=None if self.settings_tab.console else subprocess.PIPE,
stderr=None if self.settings_tab.console else subprocess.PIPE,
text=True,
bufsize=1,
universal_newlines=True,
creationflags=show_console
)
while True:
if not self.is_downloading:
if self.find_download_event(stdout, wsid):
self.is_downloading = True
if process.poll() != None:
break
time.sleep(1)
# print("Broken freeeee!")
self.is_downloading = False
try:
with open(stdout, 'w') as file:
file.write('')
except:
os.rename(stdout, os.path.join(map_folder, os.path.join(stdout, f"workshop_log_couldntremove_{timestamp}.txt")))
if not os.path.exists(map_folder):
show_message("SteamCMD has terminated", "SteamCMD has been terminated\nAnd failed to download the map/mod, try again or enable continuous download in settings")
self.settings_tab.stopped = True
if not queue:
self.button_download.configure(state="normal")
self.button_stop.configure(state="disabled")
return process.returncode
def download_map(self): def download_map(self):
self.is_downloading = False
if not self.is_pressed: if not self.is_pressed:
self.is_pressed = True self.is_pressed = True
self.library_tab.load_items(self.edit_destination_folder.get())
if self.queue_enabled: if self.queue_enabled:
start_down_thread = threading.Thread(target=self.queue_download_thread) start_down_thread = threading.Thread(target=self.queue_download_thread)
start_down_thread.start() start_down_thread.start()
@ -1715,8 +1807,7 @@ class BOIIIWD(ctk.CTk):
show_message("Warning", "Already pressed, Please wait.") show_message("Warning", "Already pressed, Please wait.")
def queue_download_thread(self): def queue_download_thread(self):
global stopped self.stopped = False
stopped = False
self.queue_stop_button = False self.queue_stop_button = False
try: try:
save_config("DestinationFolder" ,self.edit_destination_folder.get()) save_config("DestinationFolder" ,self.edit_destination_folder.get())
@ -1762,6 +1853,7 @@ class BOIIIWD(ctk.CTk):
return return
self.total_queue_size = 0 self.total_queue_size = 0
self.already_installed = []
for item in items: for item in items:
item.strip() item.strip()
workshop_id = item workshop_id = item
@ -1791,16 +1883,32 @@ class BOIIIWD(ctk.CTk):
self.stop_download self.stop_download
return return
if any(workshop_id in item for item in self.library_tab.added_items):
self.already_installed.append(workshop_id)
if self.already_installed:
item_ids = ", ".join(self.already_installed)
if self.settings_tab.skip_already_installed:
for item in self.already_installed:
if item in items:
items.remove(item)
show_message("Heads up!, map/s skipped => skip is on in settings", f"These item IDs may already be installed and are skipped:\n{item_ids}", icon="info")
if not any(isinstance(item, int) for item in items):
self.stop_download
return
show_message("Heads up! map/s not skipped => skip is off in settings", f"These item IDs may already be installed:\n{item_ids}", icon="info")
self.after(1, self.status_text.configure(text=f"Status: Total size: ~{convert_bytes_to_readable(self.total_queue_size)}")) self.after(1, self.status_text.configure(text=f"Status: Total size: ~{convert_bytes_to_readable(self.total_queue_size)}"))
start_time = time.time() start_time = time.time()
for index, item in enumerate(items): for index, item in enumerate(items):
self.settings_tab.steam_fail_counter = 0
current_number = index + 1 current_number = index + 1
total_items = len(items) total_items = len(items)
if self.queue_stop_button: if self.queue_stop_button:
self.stop_download self.stop_download
break break
item.strip() item.strip()
stopped = False self.settings_tab.stopped = False
workshop_id = item workshop_id = item
if not workshop_id.isdigit(): if not workshop_id.isdigit():
try: try:
@ -1823,17 +1931,14 @@ 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, steamcmd_reset
previous_net_speed = 0 previous_net_speed = 0
est_downloaded_bytes = 0 est_downloaded_bytes = 0
file_size = ws_file_size file_size = ws_file_size
item_name = get_item_name(workshop_id) if get_item_name(workshop_id) else "Error getting name" item_name = get_item_name(workshop_id) if get_item_name(workshop_id) else "Error getting name"
while not stopped: while not self.settings_tab.stopped:
if steamcmd_reset: if self.settings_tab.steamcmd_reset:
steamcmd_reset = False self.settings_tab.steamcmd_reset = False
previous_net_speed = 0 previous_net_speed = 0
est_downloaded_bytes = 0 est_downloaded_bytes = 0
@ -1856,7 +1961,19 @@ class BOIIIWD(ctk.CTk):
text=f"Status: Total size: ~{convert_bytes_to_readable(self.total_queue_size)} | ID: {workshop_id} | {item_name} | Downloading {current_number}/{total_items}")) text=f"Status: Total size: ~{convert_bytes_to_readable(self.total_queue_size)} | ID: {workshop_id} | {item_name} | Downloading {current_number}/{total_items}"))
self.after(1, lambda p=progress: self.label_file_size.configure(text=f"Wrong size reported\nFile size: ~{convert_bytes_to_readable(current_size)}")) self.after(1, lambda p=progress: self.label_file_size.configure(text=f"Wrong size reported\nFile size: ~{convert_bytes_to_readable(current_size)}"))
if estimated_progress: while not self.is_downloading:
self.after(1, self.label_speed.configure(text=f"Network Speed: 0 KB/s"))
time_elapsed = time.time() - start_time
elapsed_hours, elapsed_minutes, elapsed_seconds = convert_seconds(time_elapsed)
if self.settings_tab.show_fails:
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} - Fails: {self.settings_tab.steam_fail_counter}"))
else:
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)
if self.is_downloading:
break
if self.settings_tab.estimated_progress:
time_elapsed = time.time() - start_time time_elapsed = time.time() - start_time
raw_net_speed = psutil.net_io_counters().bytes_recv raw_net_speed = psutil.net_io_counters().bytes_recv
@ -1885,8 +2002,8 @@ class BOIIIWD(ctk.CTk):
self.after(1, self.progress_bar.set(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 v=net_speed: self.label_speed.configure(text=f"Network Speed: {v:.2f} {speed_unit}"))
self.after(1, lambda p=min(percentage_complete ,99): self.progress_text.configure(text=f"{p:.2f}%")) self.after(1, lambda p=min(percentage_complete ,99): self.progress_text.configure(text=f"{p:.2f}%"))
if show_fails: if self.settings_tab.show_fails:
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} - Fails: {steam_fail_counter}")) 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} - Fails: {self.settings_tab.steam_fail_counter}"))
else: else:
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}")) 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}"))
@ -1908,14 +2025,14 @@ class BOIIIWD(ctk.CTk):
text=f"Status: Total size: ~{convert_bytes_to_readable(self.total_queue_size)} | ID: {workshop_id} | {item_name} | Downloading {current_number}/{total_items}")) text=f"Status: Total size: ~{convert_bytes_to_readable(self.total_queue_size)} | ID: {workshop_id} | {item_name} | Downloading {current_number}/{total_items}"))
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}%"))
if show_fails: if self.settings_tab.show_fails:
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} - Fails: {steam_fail_counter}")) 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} - Fails: {self.settings_tab.steam_fail_counter}"))
else: else:
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}")) 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 +@sSteamCmdForcePlatformBitness 64 +app_info_update 1 +app_info_print 311210 app_update 311210 +workshop_download_item 311210 {workshop_id} validate +quit" command = f"+login anonymous app_update 311210 +workshop_download_item 311210 {workshop_id} validate +quit"
steamcmd_thread = threading.Thread(target=lambda: run_steamcmd_command(command, self, map_folder, queue=True)) steamcmd_thread = threading.Thread(target=lambda: self.run_steamcmd_command(command, map_folder, workshop_id, queue=True))
steamcmd_thread.start() steamcmd_thread.start()
def wait_for_threads(): def wait_for_threads():
@ -1954,12 +2071,12 @@ class BOIIIWD(ctk.CTk):
except Exception as E: except Exception as E:
show_message("Error", f"Error copying files: {E}", icon="cancel") show_message("Error", f"Error copying files: {E}", icon="cancel")
if clean_on_finish: if self.settings_tab.clean_on_finish:
remove_tree(map_folder) remove_tree(map_folder)
remove_tree(download_folder) remove_tree(download_folder)
if index == len(items) - 1: if index == len(items) - 1:
msg = CTkMessagebox(title="Downloads Complete", message=f"All files were downloaded\nYou can run the game now!", icon="info", option_1="Launch", option_2="Ok") msg = CTkMessagebox(title="Downloads Complete", message=f"All files were downloaded\nYou can run the game now!\nPS: You have to restart the game \n(pressing launch will launch/restarts)", icon="info", option_1="Launch", option_2="Ok")
response = msg.get() response = msg.get()
if response=="Launch": if response=="Launch":
launch_boiii_func(self.edit_destination_folder.get().strip()) launch_boiii_func(self.edit_destination_folder.get().strip())
@ -1978,20 +2095,18 @@ class BOIIIWD(ctk.CTk):
self.button_stop.configure(state="disabled") self.button_stop.configure(state="disabled")
self.after(1, self.status_text.configure(text=f"Status: Done")) self.after(1, self.status_text.configure(text=f"Status: Done"))
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"))
stopped = True self.settings_tab.stopped = True
self.stop_download self.stop_download
return return
finally: finally:
global steam_fail_counter self.settings_tab.steam_fail_counter = 0
steam_fail_counter = 0
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"))
self.stop_download self.stop_download
self.is_pressed = False self.is_pressed = False
def download_thread(self): def download_thread(self):
try: try:
global stopped self.settings_tab.stopped = False
stopped = False
save_config("DestinationFolder" ,self.edit_destination_folder.get()) save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get()) save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
@ -2051,6 +2166,13 @@ class BOIIIWD(ctk.CTk):
self.stop_download self.stop_download
return return
if any(workshop_id in item for item in self.library_tab.added_items):
if self.settings_tab.skip_already_installed:
show_message("Heads up!, map skipped => Skip is on in settings", f"This item may already be installed, Stopping: {workshop_id}", icon="info")
self.stop_download
return
show_message("Heads up! map not skipped => Skip is off in settings", f"This item may already be installed: {workshop_id}", icon="info")
self.after(1, lambda mid=workshop_id: self.label_file_size.configure(text=f"File size: {get_workshop_file_size(mid ,raw=True)}")) self.after(1, lambda mid=workshop_id: self.label_file_size.configure(text=f"File size: {get_workshop_file_size(mid ,raw=True)}"))
download_folder = os.path.join(get_steamcmd_path(), "steamapps", "workshop", "downloads", "311210", workshop_id) download_folder = os.path.join(get_steamcmd_path(), "steamapps", "workshop", "downloads", "311210", workshop_id)
map_folder = os.path.join(get_steamcmd_path(), "steamapps", "workshop", "content", "311210", workshop_id) map_folder = os.path.join(get_steamcmd_path(), "steamapps", "workshop", "content", "311210", workshop_id)
@ -2058,17 +2180,14 @@ 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, steamcmd_reset
previous_net_speed = 0 previous_net_speed = 0
est_downloaded_bytes = 0 est_downloaded_bytes = 0
start_time = time.time() start_time = time.time()
file_size = ws_file_size file_size = ws_file_size
while not stopped: while not self.settings_tab.stopped:
if steamcmd_reset: if self.settings_tab.steamcmd_reset:
steamcmd_reset = False self.settings_tab.steamcmd_reset = False
previous_net_speed = 0 previous_net_speed = 0
est_downloaded_bytes = 0 est_downloaded_bytes = 0
@ -2087,7 +2206,19 @@ class BOIIIWD(ctk.CTk):
file_size = current_size 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)}")) 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: while not self.is_downloading:
self.after(1, self.label_speed.configure(text=f"Network Speed: 0 KB/s"))
time_elapsed = time.time() - start_time
elapsed_hours, elapsed_minutes, elapsed_seconds = convert_seconds(time_elapsed)
if self.settings_tab.show_fails:
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} - Fails: {self.settings_tab.steam_fail_counter}"))
else:
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)
if self.is_downloading:
break
if self.settings_tab.estimated_progress:
time_elapsed = time.time() - start_time time_elapsed = time.time() - start_time
raw_net_speed = psutil.net_io_counters().bytes_recv raw_net_speed = psutil.net_io_counters().bytes_recv
@ -2115,8 +2246,8 @@ class BOIIIWD(ctk.CTk):
self.after(1, self.progress_bar.set(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 v=net_speed: self.label_speed.configure(text=f"Network Speed: {v:.2f} {speed_unit}"))
self.after(1, lambda p=min(percentage_complete ,99): self.progress_text.configure(text=f"{p:.2f}%")) self.after(1, lambda p=min(percentage_complete ,99): self.progress_text.configure(text=f"{p:.2f}%"))
if show_fails: if self.settings_tab.show_fails:
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} - Fails: {steam_fail_counter}")) 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} - Fails: {self.settings_tab.steam_fail_counter}"))
else: else:
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}")) 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}"))
@ -2136,14 +2267,14 @@ class BOIIIWD(ctk.CTk):
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}%"))
if show_fails: if self.settings_tab.show_fails:
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} - Fails: {steam_fail_counter}")) 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} - Fails: {self.settings_tab.steam_fail_counter}"))
else: else:
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}")) 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 +@sSteamCmdForcePlatformBitness 64 +app_info_update 1 +app_info_print 311210 app_update 311210 +workshop_download_item 311210 {workshop_id} validate +quit" command = f"+login anonymous app_update 311210 +workshop_download_item 311210 {workshop_id} validate +quit"
steamcmd_thread = threading.Thread(target=lambda: run_steamcmd_command(command, self, map_folder)) steamcmd_thread = threading.Thread(target=lambda: self.run_steamcmd_command(command, map_folder, workshop_id))
steamcmd_thread.start() steamcmd_thread.start()
def wait_for_threads(): def wait_for_threads():
@ -2152,8 +2283,7 @@ class BOIIIWD(ctk.CTk):
update_ui_thread.start() update_ui_thread.start()
update_ui_thread.join() update_ui_thread.join()
global stopped self.settings_tab.stopped = True
stopped = True
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%")
@ -2185,11 +2315,11 @@ class BOIIIWD(ctk.CTk):
except Exception as E: except Exception as E:
show_message("Error", f"Error copying files: {E}", icon="cancel") show_message("Error", f"Error copying files: {E}", icon="cancel")
if clean_on_finish: if self.settings_tab.clean_on_finish:
remove_tree(map_folder) remove_tree(map_folder)
remove_tree(download_folder) remove_tree(download_folder)
msg = CTkMessagebox(title="Download Complete", message=f"{mod_type.capitalize()} files were downloaded\nYou can run the game now!", icon="info", option_1="Launch", option_2="Ok") msg = CTkMessagebox(title="Download Complete", message=f"{mod_type.capitalize()} files were downloaded\nYou can run the game now!\nPS: You have to restart the game \n(pressing launch will launch/restarts)", icon="info", option_1="Launch", option_2="Ok")
response = msg.get() response = msg.get()
if response=="Launch": if response=="Launch":
launch_boiii_func(self.edit_destination_folder.get().strip()) launch_boiii_func(self.edit_destination_folder.get().strip())
@ -2206,17 +2336,16 @@ class BOIIIWD(ctk.CTk):
self.button_stop.configure(state="normal") self.button_stop.configure(state="normal")
finally: finally:
global steam_fail_counter self.settings_tab.steam_fail_counter = 0
steam_fail_counter = 0
self.stop_download self.stop_download
self.is_pressed = False self.is_pressed = False
def stop_download(self, on_close=None): def stop_download(self, on_close=None):
global stopped, steam_fail_counter self.settings_tab.stopped = True
stopped = True
self.queue_stop_button = True self.queue_stop_button = True
steam_fail_counter = 0 self.settings_tab.steam_fail_counter = 0
self.is_pressed = False self.is_pressed = False
self.is_downloading = False
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"))
if on_close: if on_close:

BIN
dist/BOIIIWD.exe vendored

Binary file not shown.