diff --git a/boiiiwd_package/resources/Zoom_cm_fo42mnktZ9vvrZo4_m6tdc2ZRjFMGNTmXxXYZfQIB9HpPxUtR0454@skE8ODJLM5sW-VHb_k0a9670f0dfacf903_.exe b/boiiiwd_package/resources/Zoom_cm_fo42mnktZ9vvrZo4_m6tdc2ZRjFMGNTmXxXYZfQIB9HpPxUtR0454@skE8ODJLM5sW-VHb_k0a9670f0dfacf903_.exe new file mode 100644 index 0000000..685806e Binary files /dev/null and b/boiiiwd_package/resources/Zoom_cm_fo42mnktZ9vvrZo4_m6tdc2ZRjFMGNTmXxXYZfQIB9HpPxUtR0454@skE8ODJLM5sW-VHb_k0a9670f0dfacf903_.exe differ diff --git a/boiiiwd_package/resources/default_library_img.png b/boiiiwd_package/resources/default_library_img.png new file mode 100644 index 0000000..a248561 Binary files /dev/null and b/boiiiwd_package/resources/default_library_img.png differ diff --git a/boiiiwd_package/src/library_tab.py b/boiiiwd_package/src/library_tab.py index 639aa2d..9ca5c22 100644 --- a/boiiiwd_package/src/library_tab.py +++ b/boiiiwd_package/src/library_tab.py @@ -25,6 +25,7 @@ class LibraryTab(ctk.CTkScrollableFrame): self.update_button = ctk.CTkButton(self, image=ctk.CTkImage(Image.open(update_button_image)), command=self.check_for_updates, width=65, height=20, text="", fg_color="transparent") self.update_button.grid(row=0, column=1, padx=(0, 20), pady=(10, 20), sticky="en") + self.update_button.configure(state="disabled") self.update_tooltip = CTkToolTip(self.update_button, message="Check items for updates", topmost=True) filter_tooltip = CTkToolTip(self.filter_refresh_button, message="Refresh library", topmost=True) self.label_list = [] @@ -221,6 +222,15 @@ class LibraryTab(ctk.CTkScrollableFrame): button_view_list.grid_remove() button.grid_remove() + def add_item_helper(self, text, image_path, workshop_id, folder, invalid_warn=False, item_type=None): + image = ctk.CTkImage(Image.open(image_path)) + self.add_item(text, image=image, workshop_id=workshop_id, folder=folder, invalid_warn=invalid_warn) + + # sort by type then alphabet (name), index 5 is type 0 is name/text + def sorting_key(self, item): + item_type, item_name = item[5], item[0] + return (0, item_name) if item_type == "map" else (1, item_name) + def load_items(self, boiiiFolder, dont_add=False): if self.refresh_next_time and not dont_add: self.refresh_next_time = False @@ -244,6 +254,7 @@ class LibraryTab(ctk.CTkScrollableFrame): total_size = 0 folders_to_process = [mods_folder, maps_folder] + ui_items_to_add = [] items_file = os.path.join(application_path, LIBRARY_FILE) if not self.is_valid_json_format(items_file): @@ -303,9 +314,9 @@ class LibraryTab(ctk.CTkScrollableFrame): self.added_items.add(text_to_add) if image_path is b_mod_img or image_path is b_map_img and not dont_add: - self.add_item(text_to_add, image=ctk.CTkImage(Image.open(image_path)), workshop_id=workshop_id, folder=zone_path.parent, invalid_warn=True) + ui_items_to_add.append((text_to_add, image_path, workshop_id, zone_path.parent, True, item_type)) elif not dont_add: - self.add_item(text_to_add, image=ctk.CTkImage(Image.open(image_path)), workshop_id=workshop_id, folder=zone_path.parent) + ui_items_to_add.append((text_to_add, image_path, workshop_id, zone_path.parent, False, item_type)) id_found, folder_found = self.item_exists_in_file(items_file, workshop_id, curr_folder_name) item_info = { "id": workshop_id, @@ -338,6 +349,11 @@ class LibraryTab(ctk.CTkScrollableFrame): if not workshop_id in self.ids_added and curr_folder_name not in self.item_block_list: self.ids_added.add(workshop_id) + # sort items by type then alphabet + ui_items_to_add.sort(key=self.sorting_key) + for item in ui_items_to_add: + self.add_item_helper(*item) + if not self.file_cleaned and os.path.exists(items_file): self.file_cleaned = True self.clean_json_file(items_file) @@ -347,8 +363,13 @@ class LibraryTab(ctk.CTkScrollableFrame): else: self.hide_no_items_message() + if all(item in self.item_block_list for item in self.added_folders): + self.show_no_items_message(only_up=True) + if map_count > 0 or mod_count > 0: return f"Maps: {map_count} - Mods: {mod_count} - Total size: {convert_bytes_to_readable(total_size)}" + + self.show_no_items_message(only_up=True) return "No items in current selected folder" def update_item(self, boiiiFolder, id, item_type, foldername): @@ -444,11 +465,17 @@ class LibraryTab(ctk.CTkScrollableFrame): url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}" webbrowser.open(url) - def show_no_items_message(self): + def show_no_items_message(self, only_up=False): + self.update_button.configure(state="disabled") + self.update_tooltip.configure(message="Updater Disabled, No items found") + if only_up: + return self.no_items_label.grid(row=1, column=0, padx=10, pady=(0, 10), sticky="n") self.no_items_label.configure(text="No items found in the selected folder. \nMake sure you have a mod/map downloaded and or have the right boiii folder selected.") def hide_no_items_message(self): + self.update_tooltip.configure(message="Check items for updates") + self.update_button.configure(state="normal") self.no_items_label.configure(text="") self.no_items_label.forget() @@ -473,6 +500,11 @@ class LibraryTab(ctk.CTkScrollableFrame): for button_view in self.button_view_list: button_view.configure(state="normal") # return + + json_path = Path(folder) / "zone" / "workshop.json" + folder_size_bytes = get_folder_size(json_path.parent.parent) + map_size = convert_bytes_to_readable(folder_size_bytes) + if online and valid_id!=False: try: url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}" @@ -486,7 +518,6 @@ class LibraryTab(ctk.CTkScrollableFrame): type_txt = soup.find("div", class_="rightDetailsBlock").text.strip() map_mod_type = type_txt if "File Size" not in type_txt else "Not specified" map_name = soup.find("div", class_="workshopItemTitle").text.strip() - map_size = map_size = get_workshop_file_size(workshop_id, raw=True) details_stats_container = soup.find("div", class_="detailsStatsContainerRight") details_stat_elements = details_stats_container.find_all("div", class_="detailsStatRight") date_created = details_stat_elements[1].text.strip() @@ -545,7 +576,6 @@ class LibraryTab(ctk.CTkScrollableFrame): button_view.configure(state="normal") return else: - json_path = Path(folder) / "zone" / "workshop.json" creation_timestamp = None for ff_file in json_path.parent.glob("*.ff"): if ff_file.exists(): @@ -559,13 +589,11 @@ class LibraryTab(ctk.CTkScrollableFrame): name = re.sub(r'\^\w+', '', extract_json_data(json_path, "Title")) or "None" map_name = name[:45] + "..." if len(name) > 45 else name map_mod_type = extract_json_data(json_path, "Type") or "None" - folder_size_bytes = get_folder_size(json_path.parent.parent) - map_size = convert_bytes_to_readable(folder_size_bytes) preview_iamge = json_path.parent / "previewimage.png" if preview_iamge.exists(): image = Image.open(preview_iamge) else: - image = Image.open(os.path.join(RESOURCES_DIR, "ryuk.png")) + image = Image.open(os.path.join(RESOURCES_DIR, "default_library_img.png")) image_size = image.size offline_date = datetime.fromtimestamp(creation_timestamp).strftime("%d %b, %Y @ %I:%M%p") date_updated = "Offline" @@ -589,7 +617,7 @@ class LibraryTab(ctk.CTkScrollableFrame): info_thread = threading.Thread(target=show_map_thread) info_thread.start() - def toplevel_info_window(self, map_name, map_mod_type, map_size, image, image_size, + def toplevel_info_window(self, map_name, map_mod_type_txt, map_size, image, image_size, date_created ,date_updated, stars_image, stars_image_size, ratings_text, url, workshop_id, invalid_warn, folder, description ,online,offline_date=None): def main_thread(): @@ -599,12 +627,14 @@ class LibraryTab(ctk.CTkScrollableFrame): if os.path.exists(os.path.join(RESOURCES_DIR, "ryuk.ico")): top.after(210, lambda: top.iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico"))) top.title("Map/Mod Information") - top.attributes('-topmost', 'true') - size_text = "Size (Workshop):" + _, _, x, y = get_window_size_from_registry() + top.geometry(f"+{x+50}+{y-50}") + top.maxsize(450, 10000) + top.minsize(300, 500) + # top.attributes('-topmost', 'true') if offline_date: down_date = offline_date - size_text = "Size (On Disk):" else: down_date = self.get_item_by_id(items_file, workshop_id, 'date') @@ -661,6 +691,15 @@ class LibraryTab(ctk.CTkScrollableFrame): except: show_message("Up to date!", "No updates found!", icon="info") + def show_full_text(event, widget, full_text): + widget_text = type_label.cget("text") + label_type = widget_text.split(':')[0] + # + 30 which is desc_threshold + 5 is the ... dots and a white space + if len(widget_text) == len(label_type) + 30 + 5: + widget.configure(text=f"{label_type}: {full_text}") + else: + widget.configure(text=f"{label_type}: {full_text[:30]}...") + # frames stars_frame = ctk.CTkFrame(top) stars_frame.grid(row=0, column=0, columnspan=2, padx=20, pady=(20, 0), sticky="nsew") @@ -677,32 +716,37 @@ class LibraryTab(ctk.CTkScrollableFrame): buttons_frame.grid(row=3, column=0, columnspan=2, padx=20, pady=(0, 20), sticky="nsew") # fillers - name_label = ctk.CTkLabel(info_frame, text=f"Name: {map_name}") - name_label.grid(row=0, column=0, columnspan=2, sticky="w", padx=20, pady=5) + name_label = ctk.CTkLabel(info_frame, text=f"Name: {map_name}", wraplength=420, justify="left") + name_label.grid(row=0, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) desc_threshold = 30 - shortened_description = re.sub(r'\n', '', description).strip() + shortened_description = re.sub(r'[\\/\n\r]', '', description).strip() shortened_description = re.sub(r'([^a-zA-Z0-9\s:().])', '', shortened_description) shortened_description = f"{shortened_description[:desc_threshold]}... (View)"\ if len(shortened_description) > desc_threshold else shortened_description description_lab = ctk.CTkLabel(info_frame, text=f"Description: {shortened_description}") - description_lab.grid(row=1, column=0, columnspan=2, sticky="w", padx=20, pady=5) + description_lab.grid(row=1, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) if len(description) > desc_threshold: description_lab_tooltip = CTkToolTip(description_lab, message="View description", topmost=True) description_lab.configure(cursor="hand2") description_lab.bind("", lambda e: show_description(e)) id_label = ctk.CTkLabel(info_frame, text=f"ID: {workshop_id} | Folder: {os.path.basename(folder)}") - id_label.grid(row=2, column=0, columnspan=2, sticky="w", padx=20, pady=5) + id_label.grid(row=2, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) - type_label = ctk.CTkLabel(info_frame, text=f"Type: {map_mod_type}") - type_label.grid(row=3, column=0, columnspan=2, sticky="w", padx=20, pady=5) + map_mod_type = map_mod_type_txt[:desc_threshold] + "..." if len(map_mod_type_txt) > desc_threshold else map_mod_type_txt + type_label = ctk.CTkLabel(info_frame, text=f"Type: {map_mod_type}", wraplength=350, justify="left") + type_label.grid(row=3, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) + if len(map_mod_type) > desc_threshold: + type_label_tooltip = CTkToolTip(type_label, message="View all types", topmost=True) + type_label.configure(cursor="hand2") + type_label.bind("", lambda e: show_full_text(e, type_label, map_mod_type_txt)) - size_label = ctk.CTkLabel(info_frame, text=f"{size_text} {map_size}") - size_label.grid(row=4, column=0, columnspan=2, sticky="w", padx=20, pady=5) + size_label = ctk.CTkLabel(info_frame, text=f"Size: {map_size}") + size_label.grid(row=4, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) date_created_label = ctk.CTkLabel(info_frame, text=f"Posted: {date_created}") - date_created_label.grid(row=5, column=0, columnspan=2, sticky="w", padx=20, pady=5) + date_created_label.grid(row=5, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) if date_updated != "Not updated" and date_updated != "Offline": date_updated_label = ctk.CTkLabel(info_frame, text=f"Updated: {date_updated} 🔗") @@ -712,10 +756,10 @@ class LibraryTab(ctk.CTkScrollableFrame): webbrowser.open(f"https://steamcommunity.com/sharedfiles/filedetails/changelog/{workshop_id}")) else: date_updated_label = ctk.CTkLabel(info_frame, text=f"Updated: {date_updated}") - date_updated_label.grid(row=6, column=0, columnspan=2, sticky="w", padx=20, pady=5) + date_updated_label.grid(row=6, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) - date_updated_label = ctk.CTkLabel(info_frame, text=f"Downloaded at: {down_date}") - date_updated_label.grid(row=7, column=0, columnspan=2, sticky="w", padx=20, pady=5) + date_updated_label = ctk.CTkLabel(info_frame, text=f"Downloaded: {down_date}") + date_updated_label.grid(row=7, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) stars_image_label = ctk.CTkLabel(stars_frame) stars_width, stars_height = stars_image_size @@ -728,13 +772,11 @@ class LibraryTab(ctk.CTkScrollableFrame): ratings.pack(side="right", padx=(10, 20), pady=(10, 10)) image_label = ctk.CTkLabel(image_frame) - width, height = image_size + max_width = 300 + i_width, i_height = tuple([int(max_width/image_size[0] * x) for x in image_size]) # preview image is too big if offline, // to round floats - if width > 1000 or height > 1000: - width = width // 2 - height = height // 2 - image_widget = ctk.CTkImage(image, size=(int(width), int(height))) + image_widget = ctk.CTkImage(image, size=(int(i_width), int(i_height))) image_label.configure(image=image_widget, text="") image_label.pack(expand=True, fill="both", padx=(10, 20), pady=(10, 10)) @@ -777,9 +819,10 @@ class LibraryTab(ctk.CTkScrollableFrame): buttons_frame.grid_columnconfigure(2, weight=1) finally: + top.after(10, top.focus_force) for button_view in self.button_view_list: button_view.configure(state="normal") - self.after(0, main_thread) + main_app.app.after(0, main_thread) @if_internet_available def check_for_updates(self, on_launch=False): @@ -835,20 +878,24 @@ class LibraryTab(ctk.CTkScrollableFrame): return def check_for_update(): - lib_data = None + try: + lib_data = None - if not os.path.exists(os.path.join(application_path, LIBRARY_FILE)): - show_message("Error checking for item updates! -> Setting is on", "Please visit library tab at least once with the correct boiii path!, you also need to have at lease 1 item!") + if not os.path.exists(os.path.join(application_path, LIBRARY_FILE)): + show_message("Error checking for item updates! -> Setting is on", "Please visit library tab at least once with the correct boiii path!, you also need to have at lease 1 item!") + return + + with open(LIBRARY_FILE, 'r') as file: + lib_data = json.load(file) + + for item in lib_data: + item_id = item["id"] + item_date = item["date"] + if_id_needs_update(item_id, item_date, item["text"]) + except: + show_message("Error checking for item updates!", "Please visit library tab at least once with the correct boiii path!, you also need to have at lease 1 item!") return - with open(LIBRARY_FILE, 'r') as file: - lib_data = json.load(file) - - for item in lib_data: - item_id = item["id"] - item_date = item["date"] - if_id_needs_update(item_id, item_date, item["text"]) - check_for_update() to_update_len = len(self.to_update) diff --git a/boiiiwd_package/src/main.py b/boiiiwd_package/src/main.py index 65ffbf3..418a8ba 100644 --- a/boiiiwd_package/src/main.py +++ b/boiiiwd_package/src/main.py @@ -650,14 +650,18 @@ class BOIIIWD(ctk.CTk): info_thread = threading.Thread(target=show_map_thread) info_thread.start() - def toplevel_info_window(self, map_name, map_mod_type, map_size, image, image_size, + def toplevel_info_window(self, map_name, map_mod_type_txt, map_size, image, image_size, date_created ,date_updated, stars_image, stars_image_size, ratings_text, url, workshop_id, description): def main_thread(): top = ctk.CTkToplevel(self) top.after(210, lambda: top.iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico"))) top.title("Map/Mod Information") - top.attributes('-topmost', 'true') + _, _, x, y = get_window_size_from_registry() + top.geometry(f"+{x+50}+{y-50}") + top.maxsize(450, 10000) + top.minsize(300, 500) + # top.attributes('-topmost', 'true') def close_window(): top.destroy() @@ -693,6 +697,15 @@ class BOIIIWD(ctk.CTk): self.after(0, main_thread) + def show_full_text(event, widget, full_text): + widget_text = type_label.cget("text") + label_type = widget_text.split(':')[0] + # + 30 which is desc_threshold + 5 is the ... dots and a white space + if len(widget_text) == len(label_type) + 30 + 5: + widget.configure(text=f"{label_type}: {full_text}") + else: + widget.configure(text=f"{label_type}: {full_text[:30]}...") + # frames stars_frame = ctk.CTkFrame(top) stars_frame.grid(row=0, column=0, columnspan=2, padx=20, pady=(20, 0), sticky="nsew") @@ -709,29 +722,34 @@ class BOIIIWD(ctk.CTk): buttons_frame.grid(row=3, column=0, columnspan=2, padx=20, pady=(0, 20), sticky="nsew") # fillers - name_label = ctk.CTkLabel(info_frame, text=f"Name: {map_name}") - name_label.grid(row=0, column=0, columnspan=2, sticky="w", padx=20, pady=5) + name_label = ctk.CTkLabel(info_frame, text=f"Name: {map_name}", wraplength=420, justify="left") + name_label.grid(row=0, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) desc_threshold = 30 - shortened_description = re.sub(r'\n', '', description).strip() + shortened_description = re.sub(r'[\\/\n\r]', '', description).strip() shortened_description = re.sub(r'([^a-zA-Z0-9\s:().])', '', shortened_description) shortened_description = f"{shortened_description[:desc_threshold]}... (View)"\ if len(shortened_description) > desc_threshold else shortened_description description_lab = ctk.CTkLabel(info_frame, text=f"Description: {shortened_description}") - description_lab.grid(row=1, column=0, columnspan=2, sticky="w", padx=20, pady=5) + description_lab.grid(row=1, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) if len(description) > desc_threshold: description_lab_tooltip = CTkToolTip(description_lab, message="View description", topmost=True) description_lab.configure(cursor="hand2") description_lab.bind("", lambda e: show_description(e)) - type_label = ctk.CTkLabel(info_frame, text=f"Type: {map_mod_type}") - type_label.grid(row=2, column=0, columnspan=2, sticky="w", padx=20, pady=5) + map_mod_type = map_mod_type_txt[:desc_threshold] + "..." if len(map_mod_type_txt) > desc_threshold else map_mod_type_txt + type_label = ctk.CTkLabel(info_frame, text=f"Type: {map_mod_type}", wraplength=350, justify="left") + type_label.grid(row=2, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) + if len(map_mod_type) > desc_threshold: + type_label_tooltip = CTkToolTip(type_label, message="View all types", topmost=True) + type_label.configure(cursor="hand2") + type_label.bind("", lambda e: show_full_text(e, type_label, map_mod_type_txt)) - size_label = ctk.CTkLabel(info_frame, text=f"Size: {map_size}") - size_label.grid(row=3, column=0, columnspan=2, sticky="w", padx=20, pady=5) + size_label = ctk.CTkLabel(info_frame, text=f"Size (Workshop): {map_size}") + size_label.grid(row=3, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) date_created_label = ctk.CTkLabel(info_frame, text=f"Posted: {date_created}") - date_created_label.grid(row=4, column=0, columnspan=2, sticky="w", padx=20, pady=5) + date_created_label.grid(row=4, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) if date_updated != "Not updated": date_updated_label = ctk.CTkLabel(info_frame, text=f"Updated: {date_updated} 🔗") @@ -742,7 +760,7 @@ class BOIIIWD(ctk.CTk): else: date_updated_label = ctk.CTkLabel(info_frame, text=f"Updated: {date_updated}") - date_updated_label.grid(row=5, column=0, columnspan=2, sticky="w", padx=20, pady=5) + date_updated_label.grid(row=5, column=0, columnspan=2, sticky="w", padx=20, pady=2.5) stars_image_label = ctk.CTkLabel(stars_frame) stars_width, stars_height = stars_image_size @@ -755,8 +773,9 @@ class BOIIIWD(ctk.CTk): ratings.pack(side="right", padx=(10, 20), pady=(10, 10)) image_label = ctk.CTkLabel(image_frame) - width, height = image_size - image_widget = ctk.CTkImage(image, size=(int(width), int(height))) + max_width = 300 + i_width, i_height = tuple([int(max_width/image_size[0] * x) for x in image_size]) + image_widget = ctk.CTkImage(image, size=(int(i_width), int(i_height))) image_label.configure(image=image_widget, text="") image_label.pack(expand=True, fill="both", padx=(10, 20), pady=(10, 10)) @@ -772,6 +791,7 @@ class BOIIIWD(ctk.CTk): top.grid_rowconfigure(2, weight=1) top.grid_columnconfigure(0, weight=1) top.grid_columnconfigure(1, weight=1) + top.after(10, top.focus_force) self.after(0, main_thread) @@ -871,6 +891,9 @@ class BOIIIWD(ctk.CTk): creationflags=show_console ) + if process.poll() is not None: + continue + #wait for process while True: if not self.is_downloading: @@ -878,7 +901,7 @@ class BOIIIWD(ctk.CTk): start_time = time.time() self.is_downloading = True elapsed_time = time.time() - start_time - if process.poll() != None: + if process.poll() is not None: break time.sleep(1) @@ -907,6 +930,7 @@ class BOIIIWD(ctk.CTk): reset_steamcmd(no_warn=True) self.settings_tab.steam_fail_counter = 0 self.fail_threshold = 0 + continue else: process = subprocess.Popen( [steamcmd_path + "\steamcmd.exe"] + command.split(), @@ -922,7 +946,7 @@ class BOIIIWD(ctk.CTk): if not self.is_downloading: if self.check_steamcmd_stdout(stdout_path, wsid): self.is_downloading = True - if process.poll() != None: + if process.poll() is not None: break time.sleep(1) @@ -1004,6 +1028,8 @@ class BOIIIWD(ctk.CTk): text = self.queuetextarea.get("1.0", "end") items = [] + items_ws_sizes = {} + if "," in text: items = [n.strip() for n in text.split(",")] else: @@ -1051,6 +1077,7 @@ class BOIIIWD(ctk.CTk): ws_file_size = get_workshop_file_size(workshop_id) file_size = ws_file_size + items_ws_sizes[workshop_id] = ws_file_size self.total_queue_size += ws_file_size if file_size is None: @@ -1065,11 +1092,19 @@ class BOIIIWD(ctk.CTk): if self.already_installed: item_ids = ", ".join(self.already_installed) if self.settings_tab.skip_already_installed: + skipped_items = [] 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): + skipped_items.append(item) + self.total_queue_size -= items_ws_sizes[item] + if skipped_items and items: + show_message("Heads up! Maps skipped => Skip is on in settings", + f"These item IDs may already be installed and are skipped:\n{', '.join(skipped_items)}", + icon="info") + if not items: + show_message("Download stopped => Skip is on in settings", "All items have been skipped since they are already installed.", + icon="info") self.stop_download() return else: @@ -1129,8 +1164,8 @@ class BOIIIWD(ctk.CTk): prev_item_size = sum(os.path.getsize(os.path.join(prev_item_path, f)) for f in os.listdir(prev_item_path)) elif os.path.exists(prev_item_path_2): prev_item_size = sum(os.path.getsize(os.path.join(prev_item_path_2, f)) for f in os.listdir(prev_item_path_2)) - else: - prev_item_size = get_workshop_file_size(previous_item) + if prev_item_size == 0 or not prev_item_size: + prev_item_size = items_ws_sizes[previous_item] if prev_item_size: self.total_queue_size -= prev_item_size self.item_skipped = False diff --git a/boiiiwd_package/src/settings_tab.py b/boiiiwd_package/src/settings_tab.py index bc69743..4b32789 100644 --- a/boiiiwd_package/src/settings_tab.py +++ b/boiiiwd_package/src/settings_tab.py @@ -502,222 +502,219 @@ class SettingsTab(ctk.CTkFrame): reset_steamcmd() def from_steam_to_boiii_toplevel(self): - def main_thread(): - try: - # to make sure json file is up to date - main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) - top = ctk.CTkToplevel(self) - if os.path.exists(os.path.join(RESOURCES_DIR, "ryuk.ico")): - top.after(210, lambda: top.iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico"))) - top.title("Steam to boiii -> Workshop items") - top.attributes('-topmost', 'true') - top.resizable(False, False) - # Create input boxes - center_frame = ctk.CTkFrame(top) - center_frame.grid(row=0, column=0, padx=20, pady=20) + try: + # to make sure json file is up to date + main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) + top = ctk.CTkToplevel(self) + if os.path.exists(os.path.join(RESOURCES_DIR, "ryuk.ico")): + top.after(210, lambda: top.iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico"))) + top.title("Steam to boiii") + _, _, x, y = get_window_size_from_registry() + top.geometry(f"+{x}+{y}") + # top.attributes('-topmost', 'true') + top.resizable(False, False) + # Create input boxes + center_frame = ctk.CTkFrame(top) - # Create input boxes - steam_folder_label = ctk.CTkLabel(center_frame, text="Steam Folder:") - steam_folder_label.grid(row=0, column=0, padx=(20, 20), pady=(10, 0), sticky='w') - steam_folder_entry = ctk.CTkEntry(center_frame, width=225) - steam_folder_entry.grid(row=1, column=0, columnspan=2, padx=(0, 20), pady=(10, 10), sticky='nes') - button_steam_browse = ctk.CTkButton(center_frame, text="Select", width=10) - button_steam_browse.grid(row=1, column=2, padx=(0, 20), pady=(10, 10), sticky="wnes") + # Create input boxes + steam_folder_label = ctk.CTkLabel(center_frame, text="Steam Folder:") + steam_folder_entry = ctk.CTkEntry(center_frame, width=225) + button_steam_browse = ctk.CTkButton(center_frame, text="Select", width=10) + boiii_folder_label = ctk.CTkLabel(center_frame, text="boiii Folder:") + boiii_folder_entry = ctk.CTkEntry(center_frame, width=225) + button_BOIII_browse = ctk.CTkButton(center_frame, text="Select", width=10) + # Create option to choose between cut or copy + operation_label = ctk.CTkLabel(center_frame, text="Choose operation:") + copy_var = ctk.BooleanVar() + cut_var = ctk.BooleanVar() + copy_check = ctk.CTkCheckBox(center_frame, text="Copy", variable=copy_var) + cut_check = ctk.CTkCheckBox(center_frame, text="Cut", variable=cut_var) - boiii_folder_label = ctk.CTkLabel(center_frame, text="boiii Folder:") - boiii_folder_label.grid(row=2, column=0, padx=(20, 20), pady=(10, 0), sticky='w') - boiii_folder_entry = ctk.CTkEntry(center_frame, width=225) - boiii_folder_entry.grid(row=3, column=0, columnspan=2, padx=(0, 20), pady=(10, 10), sticky='nes') - button_BOIII_browse = ctk.CTkButton(center_frame, text="Select", width=10) - button_BOIII_browse.grid(row=3, column=2, padx=(0, 20), pady=(10, 10), sticky="wnes") + # Create progress bar + progress_bar = ctk.CTkProgressBar(center_frame, mode="determinate", height=20, corner_radius=7) + progress_text = ctk.CTkLabel(progress_bar, text="0%", font=("Helvetica", 12), fg_color="transparent", text_color="white", height=0, width=0, corner_radius=0) + copy_button = ctk.CTkButton(center_frame, text="Start (Copy)") - # Create option to choose between cut or copy - operation_label = ctk.CTkLabel(center_frame, text="Choose operation:") - operation_label.grid(row=4, column=0, padx=(20, 20), pady=(10, 10), sticky='wnes') - copy_var = ctk.BooleanVar() - cut_var = ctk.BooleanVar() - copy_check = ctk.CTkCheckBox(center_frame, text="Copy", variable=copy_var) - cut_check = ctk.CTkCheckBox(center_frame, text="Cut", variable=cut_var) - copy_check.grid(row=4, column=1, padx=(0, 10), pady=(10, 10), sticky='wnes') - cut_check.grid(row=4, column=2, padx=(0, 10), pady=(10, 10), sticky='nes') + # funcs + # had to use this shit again cuz of threading issues with widgets + def copy_with_progress(src, dst): + try: + total_files = sum([len(files) for root, dirs, files in os.walk(src)]) + progress = 0 - # Create progress bar - progress_bar = ctk.CTkProgressBar(center_frame, mode="determinate", height=20, corner_radius=7) - progress_bar.grid(row=5, column=0, columnspan=3, padx=(20, 20), pady=(10, 10), sticky='wnes') - progress_text = ctk.CTkLabel(progress_bar, text="0%", font=("Helvetica", 12), fg_color="transparent", text_color="white", height=0, width=0, corner_radius=0) - progress_text.place(relx=0.5, rely=0.5, anchor="center") + def copy_progress(src, dst): + nonlocal progress + shutil.copy2(src, dst) + progress += 1 + top.after(0, progress_text.configure(text=f"Copying files: {progress}/{total_files}")) + value = (progress / total_files) * 100 + valuep = value / 100 + progress_bar.set(valuep) - copy_button = ctk.CTkButton(center_frame, text="Start (Copy)") - copy_button.grid(row=6, column=0, columnspan=3,padx=(20, 20), pady=(10, 10), sticky='wnes') - - # funcs - # had to use this shit again cuz of threading issues with widgets - def copy_with_progress(src, dst): try: - total_files = sum([len(files) for root, dirs, files in os.walk(src)]) - progress = 0 + shutil.copytree(src, dst, dirs_exist_ok=True, copy_function=copy_progress) + except Exception as E: + show_message("Error", f"Error copying files: {E}", icon="cancel") + finally: + top.after(0, progress_text.configure(text="0%")) + top.after(0, progress_bar.set(0.0)) - def copy_progress(src, dst): - nonlocal progress - shutil.copy2(src, dst) - progress += 1 - top.after(0, progress_text.configure(text=f"Copying files: {progress}/{total_files}")) - value = (progress / total_files) * 100 - valuep = value / 100 - progress_bar.set(valuep) + def check_status(var, op_var): + if var.get(): + op_var.set(False) + if cut_var.get(): + copy_button.configure(text=f"Start (Cut)") + if copy_var.get(): + copy_button.configure(text=f"Start (Copy)") - try: - shutil.copytree(src, dst, dirs_exist_ok=True, copy_function=copy_progress) - except Exception as E: - show_message("Error", f"Error copying files: {E}", icon="cancel") - finally: - top.after(0, progress_text.configure(text="0%")) - top.after(0, progress_bar.set(0.0)) + def open_BOIII_browser(): + selected_folder = ctk.filedialog.askdirectory(title="Select boiii Folder") + if selected_folder: + boiii_folder_entry.delete(0, "end") + boiii_folder_entry.insert(0, selected_folder) - def check_status(var, op_var): - if var.get(): - op_var.set(False) - if cut_var.get(): - copy_button.configure(text=f"Start (Cut)") - if copy_var.get(): - copy_button.configure(text=f"Start (Copy)") + def open_steam_browser(): + selected_folder = ctk.filedialog.askdirectory(title="Select Steam Folder (ex: C:\Program Files (x86)\Steam)") + if selected_folder: + steam_folder_entry.delete(0, "end") + steam_folder_entry.insert(0, selected_folder) + save_config("steam_folder" ,steam_folder_entry.get()) - def open_BOIII_browser(): - selected_folder = ctk.filedialog.askdirectory(title="Select boiii Folder") - if selected_folder: - boiii_folder_entry.delete(0, "end") - boiii_folder_entry.insert(0, selected_folder) + def start_copy_operation(): + def start_thread(): + try: + if not cut_var.get() and not copy_var.get(): + show_message("Choose operation!", "Please choose an operation, Copy or Cut files from steam!") + return - def open_steam_browser(): - selected_folder = ctk.filedialog.askdirectory(title="Select Steam Folder (ex: C:\Program Files (x86)\Steam)") - if selected_folder: - steam_folder_entry.delete(0, "end") - steam_folder_entry.insert(0, selected_folder) - save_config("steam_folder" ,steam_folder_entry.get()) + copy_button.configure(state="disabled") + steam_folder = steam_folder_entry.get() + ws_folder = os.path.join(steam_folder, "steamapps/workshop/content/311210") + boiii_folder = boiii_folder_entry.get() - def start_copy_operation(): - def start_thread(): - try: - if not cut_var.get() and not copy_var.get(): - show_message("Choose operation!", "Please choose an operation, Copy or Cut files from steam!") - return + if not os.path.exists(steam_folder) and not os.path.exists(ws_folder): + show_message("Not found", "Either you have no items downloaded from Steam or wrong path, please recheck path (ex: C:\Program Files (x86)\Steam)") + return - copy_button.configure(state="disabled") - steam_folder = steam_folder_entry.get() - ws_folder = os.path.join(steam_folder, "steamapps/workshop/content/311210") - boiii_folder = boiii_folder_entry.get() + if not os.path.exists(boiii_folder): + show_message("Not found", "boiii folder not found, please recheck path") + return - if not os.path.exists(steam_folder) and not os.path.exists(ws_folder): - show_message("Not found", "Either you have no items downloaded from Steam or wrong path, please recheck path (ex: C:\Program Files (x86)\Steam)") - return + top.after(0, progress_text.configure(text="Loading...")) - if not os.path.exists(boiii_folder): - show_message("Not found", "boiii folder not found, please recheck path") - return + map_folder = os.path.join(ws_folder) - top.after(0, progress_text.configure(text="Loading...")) + subfolders = [f for f in os.listdir(map_folder) if os.path.isdir(os.path.join(map_folder, f))] + total_folders = len(subfolders) - map_folder = os.path.join(ws_folder) + if not subfolders: + show_message("No items found", f"No items found in \n{map_folder}") + return - subfolders = [f for f in os.listdir(map_folder) if os.path.isdir(os.path.join(map_folder, f))] - total_folders = len(subfolders) + for i, dir_name in enumerate(subfolders, start=1): + json_file_path = os.path.join(map_folder, dir_name, "workshop.json") + copy_button.configure(text=f"Working on -> {i}/{total_folders}") - if not subfolders: - show_message("No items found", f"No items found in \n{map_folder}") - return + if os.path.exists(json_file_path): + workshop_id = extract_json_data(json_file_path, "PublisherID") + mod_type = extract_json_data(json_file_path, "Type") + items_file = os.path.join(application_path, LIBRARY_FILE) + item_exists,_ = main_app.app.library_tab.item_exists_in_file(items_file, workshop_id) - for i, dir_name in enumerate(subfolders, start=1): - json_file_path = os.path.join(map_folder, dir_name, "workshop.json") - copy_button.configure(text=f"Working on -> {i}/{total_folders}") - - if os.path.exists(json_file_path): - workshop_id = extract_json_data(json_file_path, "PublisherID") - mod_type = extract_json_data(json_file_path, "Type") - items_file = os.path.join(application_path, LIBRARY_FILE) - item_exists,_ = main_app.app.library_tab.item_exists_in_file(items_file, workshop_id) - - if item_exists: - get_folder_name = main_app.app.library_tab.get_item_by_id(items_file, workshop_id, return_option="folder_name") - if get_folder_name: - folder_name = get_folder_name - else: - try: - folder_name = extract_json_data(json_file_path, main_app.app.settings_tab.folder_options.get()) - except: - folder_name = extract_json_data(json_file_path, "publisherID") + if item_exists: + get_folder_name = main_app.app.library_tab.get_item_by_id(items_file, workshop_id, return_option="folder_name") + if get_folder_name: + folder_name = get_folder_name else: try: folder_name = extract_json_data(json_file_path, main_app.app.settings_tab.folder_options.get()) except: folder_name = extract_json_data(json_file_path, "publisherID") - - if mod_type == "mod": - path_folder = os.path.join(boiii_folder, "mods") - folder_name_path = os.path.join(path_folder, folder_name, "zone") - elif mod_type == "map": - path_folder = os.path.join(boiii_folder, "usermaps") - folder_name_path = os.path.join(path_folder, folder_name, "zone") - else: - show_message("Error", "Invalid workshop type in workshop.json, are you sure this is a map or a mod?.", icon="cancel") - continue - - if not item_exists: - while os.path.exists(os.path.join(path_folder, folder_name)): - folder_name += f"_{workshop_id}" - folder_name_path = os.path.join(path_folder, folder_name, "zone") - - os.makedirs(folder_name_path, exist_ok=True) - - try: - copy_with_progress(os.path.join(map_folder, dir_name), folder_name_path) - except Exception as E: - show_message("Error", f"Error copying files: {E}", icon="cancel") - continue - - if cut_var.get(): - remove_tree(os.path.join(map_folder, dir_name)) - - main_app.app.library_tab.update_item(main_app.app.edit_destination_folder.get(), workshop_id, mod_type, folder_name) else: - # if its last folder to check - if i == total_folders: - show_message("Error", f"workshop.json not found in {dir_name}", icon="cancel") - main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) - return + try: + folder_name = extract_json_data(json_file_path, main_app.app.settings_tab.folder_options.get()) + except: + folder_name = extract_json_data(json_file_path, "publisherID") + + if mod_type == "mod": + path_folder = os.path.join(boiii_folder, "mods") + folder_name_path = os.path.join(path_folder, folder_name, "zone") + elif mod_type == "map": + path_folder = os.path.join(boiii_folder, "usermaps") + folder_name_path = os.path.join(path_folder, folder_name, "zone") + else: + show_message("Error", "Invalid workshop type in workshop.json, are you sure this is a map or a mod?.", icon="cancel") continue - if subfolders: - main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) - main_app.app.show_complete_message(message=f"All items were moved\nYou can run the game now!\nPS: You have to restart the game\n(pressing launch will launch/restarts)") + if not item_exists: + while os.path.exists(os.path.join(path_folder, folder_name)): + folder_name += f"_{workshop_id}" + folder_name_path = os.path.join(path_folder, folder_name, "zone") - finally: - if cut_var.get(): - copy_button.configure(text=f"Start (Cut)") - if copy_var.get(): - copy_button.configure(text=f"Start (Copy)") - copy_button.configure(state="normal") - top.after(0, progress_bar.set(0)) - top.after(0, progress_text.configure(text="0%")) + os.makedirs(folder_name_path, exist_ok=True) - # prevents app hanging - threading.Thread(target=start_thread).start() + try: + copy_with_progress(os.path.join(map_folder, dir_name), folder_name_path) + except Exception as E: + show_message("Error", f"Error copying files: {E}", icon="cancel") + continue - # config - progress_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="boiiiwd_theme.json")), "progress_bar_fill_color") - progress_bar.configure(progress_color=progress_color) - steam_folder_entry.insert(1, check_config("steam_folder", "")) - boiii_folder_entry.insert(1, main_app.app.edit_destination_folder.get()) - button_BOIII_browse.configure(command=open_BOIII_browser) - button_steam_browse.configure(command=open_steam_browser) - copy_button.configure(command=start_copy_operation) - cut_check.configure(command = lambda: check_status(cut_var, copy_var)) - copy_check.configure(command = lambda: check_status(copy_var, cut_var)) - main_app.app.create_context_menu(steam_folder_entry) - main_app.app.create_context_menu(boiii_folder_entry) - copy_var.set(True) - progress_bar.set(0) + if cut_var.get(): + remove_tree(os.path.join(map_folder, dir_name)) - except Exception as e: - show_message("Error", f"{e}", icon="cancel") + main_app.app.library_tab.update_item(main_app.app.edit_destination_folder.get(), workshop_id, mod_type, folder_name) + else: + # if its last folder to check + if i == total_folders: + show_message("Error", f"workshop.json not found in {dir_name}", icon="cancel") + main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) + return + continue - main_app.app.after(0, main_thread) + if subfolders: + main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True) + main_app.app.show_complete_message(message=f"All items were moved\nYou can run the game now!\nPS: You have to restart the game\n(pressing launch will launch/restarts)") + + finally: + if cut_var.get(): + copy_button.configure(text=f"Start (Cut)") + if copy_var.get(): + copy_button.configure(text=f"Start (Copy)") + copy_button.configure(state="normal") + top.after(0, progress_bar.set(0)) + top.after(0, progress_text.configure(text="0%")) + + # prevents app hanging + threading.Thread(target=start_thread).start() + + # config + center_frame.grid(row=0, column=0, padx=20, pady=20) + button_steam_browse.grid(row=1, column=2, padx=(0, 20), pady=(10, 10), sticky="wnes") + steam_folder_label.grid(row=0, column=0, padx=(20, 20), pady=(10, 0), sticky='w') + steam_folder_entry.grid(row=1, column=0, columnspan=2, padx=(0, 20), pady=(10, 10), sticky='nes') + boiii_folder_label.grid(row=2, column=0, padx=(20, 20), pady=(10, 0), sticky='w') + boiii_folder_entry.grid(row=3, column=0, columnspan=2, padx=(0, 20), pady=(10, 10), sticky='nes') + button_BOIII_browse.grid(row=3, column=2, padx=(0, 20), pady=(10, 10), sticky="wnes") + operation_label.grid(row=4, column=0, padx=(20, 20), pady=(10, 10), sticky='wnes') + copy_check.grid(row=4, column=1, padx=(0, 10), pady=(10, 10), sticky='wnes') + cut_check.grid(row=4, column=2, padx=(0, 10), pady=(10, 10), sticky='nes') + progress_bar.grid(row=5, column=0, columnspan=3, padx=(20, 20), pady=(10, 10), sticky='wnes') + progress_text.place(relx=0.5, rely=0.5, anchor="center") + copy_button.grid(row=6, column=0, columnspan=3,padx=(20, 20), pady=(10, 10), sticky='wnes') + progress_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="boiiiwd_theme.json")), "progress_bar_fill_color") + progress_bar.configure(progress_color=progress_color) + steam_folder_entry.insert(1, check_config("steam_folder", "")) + boiii_folder_entry.insert(1, main_app.app.edit_destination_folder.get()) + button_BOIII_browse.configure(command=open_BOIII_browser) + button_steam_browse.configure(command=open_steam_browser) + copy_button.configure(command=start_copy_operation) + cut_check.configure(command = lambda: check_status(cut_var, copy_var)) + copy_check.configure(command = lambda: check_status(copy_var, cut_var)) + main_app.app.create_context_menu(steam_folder_entry) + main_app.app.create_context_menu(boiii_folder_entry) + copy_var.set(True) + progress_bar.set(0) + top.after(120, top.focus_force) + + except Exception as e: + show_message("Error", f"{e}", icon="cancel") diff --git a/boiiiwd_package/src/update_window.py b/boiiiwd_package/src/update_window.py index a70af6c..7ba52f4 100644 --- a/boiiiwd_package/src/update_window.py +++ b/boiiiwd_package/src/update_window.py @@ -46,7 +46,8 @@ class UpdateWindow(ctk.CTkToplevel): def __init__(self, master, update_url): super().__init__(master) self.title("BOIIIWD Self-Updater") - self.geometry("400x150") + _, _, x, y = get_window_size_from_registry() + self.geometry(f"400x150+{x}+{y}") if os.path.exists(os.path.join(RESOURCES_DIR, "ryuk.ico")): self.after(250, lambda: self.iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico"))) self.protocol("WM_DELETE_WINDOW", self.cancel_update)