Skip to content

Commit 529c868

Browse files
committed
Fixed occasional freeze
1 parent 603db43 commit 529c868

1 file changed

Lines changed: 65 additions & 44 deletions

File tree

cmd.py

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -316,18 +316,20 @@ def start_task(self):
316316
self.root.after(0, lambda: self.stop_btn.config(state="normal"))
317317
self.task_count += 1
318318

319-
def end_task(self):
319+
def end_task(self, callback=None):
320320
with self.task_lock:
321321
self.task_count -= 1
322322
if self.task_count <= 0:
323323
self.task_count = 0
324324
self.root.after(0, lambda: self.stop_btn.config(state="disabled"))
325325
self.root.after(0, self.reset_progress)
326+
if callback:
327+
self.root.after(1000, callback)
326328

327329
def stop_operation(self):
328330
self.stop_event.set()
329331
self.log("Stopping operations...", "warning")
330-
for p in self.active_processes:
332+
for p in list(self.active_processes):
331333
try: p.terminate()
332334
except: pass
333335

@@ -348,12 +350,18 @@ def start_download(self):
348350
self.progress.start(10)
349351
self.progress_label.config(text="INITIALIZING...", fg=BZ_CYAN)
350352
self.start_task()
351-
threading.Thread(target=self.download_logic, args=(mid,), daemon=True).start()
353+
354+
sc_path = self.steamcmd_var.get()
355+
cache_path = self.cache_var.get()
356+
game_path = self.path_var.get()
357+
use_physical = self.use_physical_var.get()
358+
359+
threading.Thread(target=self.download_logic, args=(mid, sc_path, cache_path, game_path, use_physical), daemon=True).start()
352360

353-
def download_logic(self, mod_id):
361+
def download_logic(self, mod_id, sc_path, cache_path, game_path, use_physical):
354362
try:
355-
self.ensure_steamcmd()
356-
cache = os.path.abspath(self.cache_var.get())
363+
final_sc_path = self.ensure_steamcmd(sc_path)
364+
cache = os.path.abspath(cache_path)
357365
mod_path = os.path.join(cache, "steamapps/workshop/content", BZ98R_APPID, mod_id)
358366

359367
if os.path.exists(mod_path):
@@ -362,7 +370,7 @@ def download_logic(self, mod_id):
362370
self.log(f"Initializing new download for Mod {mod_id}...")
363371

364372
# FIX: force_install_dir BEFORE login
365-
cmd = [self.steamcmd_var.get(), "+force_install_dir", cache, "+login", "anonymous",
373+
cmd = [final_sc_path, "+force_install_dir", cache, "+login", "anonymous",
366374
"+workshop_download_item", BZ98R_APPID, mod_id, "+quit"]
367375

368376
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, creationflags=subprocess.CREATE_NO_WINDOW)
@@ -384,11 +392,11 @@ def download_logic(self, mod_id):
384392
if p in self.active_processes: self.active_processes.remove(p)
385393

386394
src = os.path.normpath(mod_path)
387-
dst = os.path.normpath(os.path.join(self.path_var.get(), "mods", mod_id))
395+
dst = os.path.normpath(os.path.join(game_path, "mods", mod_id))
388396

389397
if os.path.exists(src):
390398
if not os.path.exists(os.path.dirname(dst)): os.makedirs(os.path.dirname(dst))
391-
if self.use_physical_var.get():
399+
if use_physical:
392400
if os.path.exists(dst): shutil.rmtree(dst)
393401
shutil.copytree(src, dst)
394402
else:
@@ -397,11 +405,9 @@ def download_logic(self, mod_id):
397405
self.root.after(0, lambda: self.dl_btn.config(text="DEPLOYED"))
398406
self.root.after(3000, lambda: self.dl_btn.config(text="INSTALL MOD", state="normal"))
399407

400-
if not self.stop_event.is_set():
401-
self.root.after(0, self.refresh_list)
402408
except Exception as e: self.log(f"CRITICAL: {e}", "error")
403409
finally:
404-
self.end_task()
410+
self.end_task(self.refresh_list if not self.stop_event.is_set() else None)
405411

406412
def update_progress(self, value):
407413
self.progress.stop()
@@ -479,11 +485,10 @@ def initialize_engine(self):
479485

480486
self.log("Ready for mod deployment.", "info")
481487

482-
def ensure_steamcmd(self):
483-
target = self.steamcmd_var.get()
488+
def ensure_steamcmd(self, target):
484489
if not target:
485490
target = os.path.join(self.bin_dir, "steamcmd.exe")
486-
self.steamcmd_var.set(target)
491+
self.root.after(0, lambda: self.steamcmd_var.set(target))
487492

488493
if not os.path.exists(target):
489494
target_dir = os.path.dirname(target)
@@ -498,6 +503,7 @@ def ensure_steamcmd(self):
498503
except Exception as e:
499504
self.log(f"SteamCMD Setup Error: {e}", "error")
500505
raise e
506+
return target
501507

502508
def check_admin(self):
503509
if not ctypes.windll.shell32.IsUserAnAdmin():
@@ -722,17 +728,20 @@ def refresh_list(self):
722728
self.progress.config(mode="indeterminate"); self.progress.start(10)
723729

724730
# Offload file system scanning to a background thread
731+
cache_path = self.cache_var.get()
732+
game_path = self.path_var.get()
733+
725734
self.start_task()
726-
threading.Thread(target=self._refresh_scan_logic, daemon=True).start()
735+
threading.Thread(target=self._refresh_scan_logic, args=(cache_path, game_path), daemon=True).start()
727736

728-
def _refresh_scan_logic(self):
737+
def _refresh_scan_logic(self, cache_path, game_path):
729738
try:
730-
base_cache = os.path.abspath(self.cache_var.get())
731-
game_path = os.path.abspath(self.path_var.get())
739+
base_cache = os.path.abspath(cache_path)
740+
game_dir = os.path.abspath(game_path)
732741

733742
# Correct nested SteamCMD structure
734743
content_dir = os.path.join(base_cache, "steamapps", "workshop", "content", BZ98R_APPID)
735-
game_mods_dir = os.path.join(game_path, "mods")
744+
game_mods_dir = os.path.join(game_dir, "mods")
736745

737746
self.log("--- SCANNING FOR ASSETS ---", "info")
738747

@@ -883,15 +892,18 @@ def enable_mod(self):
883892

884893
# Extract data on main thread
885894
mods_to_enable = [str(self.tree.item(item)['values'][1]) for item in selected]
895+
cache_path = self.cache_var.get()
896+
game_path = self.path_var.get()
897+
886898
self.start_task()
887-
threading.Thread(target=self._enable_mod_worker, args=(mods_to_enable,), daemon=True).start()
899+
threading.Thread(target=self._enable_mod_worker, args=(mods_to_enable, cache_path, game_path), daemon=True).start()
888900

889-
def _enable_mod_worker(self, mods):
901+
def _enable_mod_worker(self, mods, cache_path, game_path):
890902
try:
891903
for mid in mods:
892904
if self.stop_event.is_set(): break
893-
src = os.path.join(self.cache_var.get(), "steamapps", "workshop", "content", BZ98R_APPID, mid)
894-
dst = os.path.join(self.path_var.get(), "mods", mid)
905+
src = os.path.join(cache_path, "steamapps", "workshop", "content", BZ98R_APPID, mid)
906+
dst = os.path.join(game_path, "mods", mid)
895907

896908
try:
897909
if not os.path.exists(os.path.dirname(dst)): os.makedirs(os.path.dirname(dst))
@@ -901,25 +913,24 @@ def _enable_mod_worker(self, mods):
901913
self.log(f"Mod {mid} enabled (Junction created).", "success")
902914
except Exception as e:
903915
self.log(f"Link Error for {mid}: {e}", "error")
904-
if not self.stop_event.is_set():
905-
self.root.after(0, self.refresh_list)
906916
finally:
907-
self.end_task()
917+
self.end_task(self.refresh_list if not self.stop_event.is_set() else None)
908918

909919
def disable_mod(self):
910920
"""Disables all selected mods by removing their Junction links."""
911921
selected = self.tree.selection()
912922
if not selected: return
913923

914924
mods_to_disable = [str(self.tree.item(item)['values'][1]) for item in selected]
925+
game_path = self.path_var.get()
915926
self.start_task()
916-
threading.Thread(target=self._disable_mod_worker, args=(mods_to_disable,), daemon=True).start()
927+
threading.Thread(target=self._disable_mod_worker, args=(mods_to_disable, game_path), daemon=True).start()
917928

918-
def _disable_mod_worker(self, mods):
929+
def _disable_mod_worker(self, mods, game_path):
919930
try:
920931
for mid in mods:
921932
if self.stop_event.is_set(): break
922-
dst = os.path.join(self.path_var.get(), "mods", mid)
933+
dst = os.path.join(game_path, "mods", mid)
923934

924935
try:
925936
if os.path.lexists(dst):
@@ -932,10 +943,8 @@ def _disable_mod_worker(self, mods):
932943
self.log(f"Mod {mid} decoupled from game engine.", "info")
933944
except Exception as e:
934945
self.log(f"DECOUPLE ERROR for {mid}: {e}", "error")
935-
if not self.stop_event.is_set():
936-
self.root.after(0, self.refresh_list)
937946
finally:
938-
self.end_task()
947+
self.end_task(self.refresh_list if not self.stop_event.is_set() else None)
939948

940949
def is_junction(self, path):
941950
"""Helper to detect if a directory is a Windows Junction."""
@@ -957,9 +966,15 @@ def update_all_mods(self):
957966
return
958967

959968
self.log(f"Initializing batch update for {len(to_update)} mods...", "info")
969+
970+
sc_path = self.steamcmd_var.get()
971+
cache_path = self.cache_var.get()
972+
game_path = self.path_var.get()
973+
use_physical = self.use_physical_var.get()
974+
960975
for mid in to_update:
961976
self.start_task()
962-
threading.Thread(target=self.download_logic, args=(mid,), daemon=True).start()
977+
threading.Thread(target=self.download_logic, args=(mid, sc_path, cache_path, game_path, use_physical), daemon=True).start()
963978

964979
def delete_mod_physically(self):
965980
"""Wipes the selected mods from the SteamCMD cache and breaks any links."""
@@ -975,15 +990,17 @@ def delete_mod_physically(self):
975990

976991
if messagebox.askyesno("TERMINATE ASSET(S)", prompt_message):
977992
mods_to_delete = [str(self.tree.item(item)['values'][1]) for item in selected]
993+
cache_path = self.cache_var.get()
994+
game_path = self.path_var.get()
978995
self.start_task()
979-
threading.Thread(target=self._delete_mod_worker, args=(mods_to_delete,), daemon=True).start()
996+
threading.Thread(target=self._delete_mod_worker, args=(mods_to_delete, cache_path, game_path), daemon=True).start()
980997

981-
def _delete_mod_worker(self, mods):
998+
def _delete_mod_worker(self, mods, cache_path, game_path):
982999
try:
9831000
for mid in mods:
9841001
if self.stop_event.is_set(): break
9851002
# 1. Break Link
986-
link_path = os.path.join(self.path_var.get(), "mods", mid)
1003+
link_path = os.path.join(game_path, "mods", mid)
9871004
if os.path.lexists(link_path):
9881005
try:
9891006
if os.path.isdir(link_path):
@@ -994,18 +1011,16 @@ def _delete_mod_worker(self, mods):
9941011
self.log(f"Note: Could not remove link for {mid} during purge: {e}", "warning")
9951012

9961013
# 2. Delete Folder from cache
997-
cache_path = os.path.join(self.cache_var.get(), "steamapps/workshop/content", BZ98R_APPID, mid)
1014+
mod_cache_path = os.path.join(cache_path, "steamapps/workshop/content", BZ98R_APPID, mid)
9981015
try:
999-
if os.path.exists(cache_path):
1000-
shutil.rmtree(cache_path)
1016+
if os.path.exists(mod_cache_path):
1017+
shutil.rmtree(mod_cache_path)
10011018
self.log(f"Asset {mid} purged from local storage.", "warning")
10021019
except Exception as e:
10031020
self.log(f"Purge Error for {mid}: {e}", "error")
10041021

1005-
if not self.stop_event.is_set():
1006-
self.root.after(0, self.refresh_list)
10071022
finally:
1008-
self.end_task()
1023+
self.end_task(self.refresh_list if not self.stop_event.is_set() else None)
10091024

10101025
def update_selected_mod(self, force=False):
10111026
"""Triggers a re-download via SteamCMD for the selected mods."""
@@ -1019,8 +1034,14 @@ def update_selected_mod(self, force=False):
10191034
continue
10201035

10211036
self.log(f"Updating mod {mid}...", "info")
1037+
1038+
sc_path = self.steamcmd_var.get()
1039+
cache_path = self.cache_var.get()
1040+
game_path = self.path_var.get()
1041+
use_physical = self.use_physical_var.get()
1042+
10221043
self.start_task()
1023-
threading.Thread(target=self.download_logic, args=(mid,), daemon=True).start()
1044+
threading.Thread(target=self.download_logic, args=(mid, sc_path, cache_path, game_path, use_physical), daemon=True).start()
10241045
if __name__ == "__main__":
10251046
root = TkinterDnD.Tk() if HAS_DND else tk.Tk()
10261047
app = BZModMaster(root)

0 commit comments

Comments
 (0)