Update to latest version

This commit is contained in:
Ahrimdon 2024-02-24 23:32:30 -05:00
parent 38f74dadc3
commit 1a4f0c1292
9 changed files with 756 additions and 434 deletions

5
.gitignore vendored
View File

@ -162,5 +162,8 @@ cython_debug/
# Other
*.ini
*.conf
dist/
.vscode
test/
steamcmd
t7xwd_library.json
*.exe

119
README.md
View File

@ -1,90 +1,45 @@
# T7x Workshop Downloader (T7xWD)
- A Feature-rich GUI Steam Workshop downloader for BO3 ([T7x client](https://github.com/Ezz-lol/T7x-free)) built using CustomTkinter <br>
# T7xWD
- A Feature-rich GUI Steam Workshop downloader for [Call of Duty®: Black Ops III](https://store.steampowered.com/app/311210/Call_of_Duty_Black_Ops_III/) built using CustomTkinter <br>
<div style="display: flex; justify-content: space-between;">
<!-- Left Side -->
<div style="flex: 1; margin-right: 5px;">
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/0aa8295f-ba07-4778-8140-200021df4ba9" width="400" />
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/b4f27fe1-88f2-4158-b7ba-c8aec57b9968" width="400" />
</div>
<!-- Right Side -->
<div style="flex: 1; margin-left: 5px;">
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/86c07cf2-b04b-42d0-ae06-8526bffafb34" width="400" />
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/4c5877eb-81a7-4ae7-99db-3096ab57b12b" width="400" />
</div>
</div>
<table>
<tr>
<td align="center">
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/4d199e21-c9a0-4dfc-b831-866fbff1d1a1" max-width="400" />
</td>
<td align="center">
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/25174889-4524-455f-9836-f4ea5240e07f" max-width="400" />
</td>
<td align="center">
<img src="https://github.com/faroukbmiled/BOIIIWD/assets/51106560/df54a0d7-f9ab-4061-b8b7-06d9e5992c90" max-width="400" />
</td>
</tr>
</table>
## Usage (exe):
- Run [T7xWD.exe](https://git.rimmyscorner.com/Rim/T7x-Workshop-Downloader/releases/download/latest/T7xWD.exe) and use it (it'll ask you to download steamcmd within the app if not found)
- That's it slap in your workshop item link or just the id then hit Download and wait for it to finish, when it does just launch your game (Please check [Notes](#notes) before you ask anything)
- If the exe is getting flagged as a virus by your ac it is obviously a false positive, if you still do not trust it you can [compile/freeze](#freezing) it yourself ([VirusTotal Scan](https://www.virustotal.com/gui/file/9df159098638ab8a8bec7205eeb271cb5891c19cdbb81bcd5368dfc1ef213f76/detection))
## Usage (script):
- ```pip install -r requirements.txt``` -> use my modified [CTkToolTip](./CTkToolTip) and [CTkListbox](./CTkListbox) for v0.2.8 and up
- ```python T7xwd_package\T7xwd.py```
- Slap in your workshop item link for example: "https://steamcommunity.com/sharedfiles/filedetails/?id=3011930738" or just the id 3011930738
## Usage:
- Run [T7xWD.exe](https://github.com/faroukbmiled/T7xWD/releases/latest/download/Release.zip) ([VirusTotal Scan](https://www.virustotal.com/gui/file/5ca1367a82893a1f412b59a52431e9ac4219a67a50c294ee86a7d41473826b14/detection))
- [Optional] Run as script:```python T7xwd_package\T7xwd.py```
## Features:
- Improves steamcmd's stability while downloading
- Auto installs mods and maps to T7x
- Improved download stability
- Auto installs mods and maps
- Queue -> download items in queue
- Library tab -> lists your downloaded items
- Item updater -> Checks your items for updates (redownloads them,no way for now to "update" them only) -> Under Library tab
- Steam to T7x -> Item mover (moves items (mods,maps) from steam to T7x client) -> Under settings tab
- Themes -> Under settings tab
- Bunch of useful settings -> Under settings tab
- Item updater -> checks your items for updates
- Workshop Transfer -> copy/move items from the workshop folder into the game directory
- Custom Themes
<a name="freezing"></a>
## Freezing into an exe (pyinstaller):
- ```pip install -r requirements.txt``` -> use my modified [CTkToolTip](./CTkToolTip) and [CTkListbox](./CTkListbox) for v0.2.8 and up.
## Notes:
- Steamcmd will be downloaded if it is not installed <br>
- Initializing SteamCMD for the first time could take some time depending on your internet speed <br>
#### Mouse Bindings:
Library Tab:
* Mouse1 -> copy id
* Ctrl + Mouse1 -> append to clipboard
* Mouse2 (scroll wheel button) -> open item path in file explorer
* Mouse3 (Right click) -> copy path
## Building from Source:
- ```pip install -r requirements.txt``` -> use my modified [CTkToolTip](./CTkToolTip) and [CTkListbox](./CTkListbox)
- ```python build.py```
## Queue tab (beta)
- added Queue tab which has a text field that you can slap in workshop ids/links in 2 formats, for example:<br>
```3010399939,2976006537,2118338989```
or <br>
```3010399939
2976006537
2118338989
2113146805
```
## Hidden Config Options:
- Added a way to update invalid Items (in details) -> add ```update_invalid = yes``` to config.ini
- Added a way to download Beta items that normally will throw invalid item warning -> add ```skip_invalid = no``` to config.ini
<a name="notes"></a>
### Notes:
* It saves your input except for workshop id <br>
* If you do not know where to find your map in-game check this [video](https://youtu.be/XIQjfXXlgQs?t=260) out ,for mods find "mods" in the game's main menu <br>
* Initializing SteamCMD for the first time could take some time depending on your internet speed <br>
* New item update window: Right click on an item (mouse3) -> will open the item in the browser (Steam Workshop) <br>
* T7xWD requires having "T7xwd_library.json" in the app's directory for the new features to work -> clicking on the library tab will generate the JSON file (please don't touch it) <br>
* v0.3.1 and up program will use windows registry to save window coordinates (height , width ,x_pos, y_pos)
* For Item Updater to recognize your Item as a valid one is to have it's folder named either "FolderName ,"PublisherID" or "FolderName_PublisherID" (taken from workshop.json from each item) -> Invalid items will have a warning icon in library and will not be checked for updates -> Downloading items from T7xwd will be valid by default
* Invalid items will still work in game but will not be checked for updates
#### PS (Library tab): <br>
* Mouse1 -> copy id <br>
* Mouse2 (scroll wheel btn) -> Open item path in explorer <br>
* Mouse3 -> Copy path <br>
* Ctrl + Mouse 1 (after Mouse1) -> Append to clipboard
### Known bugs: <br>
* Rare UI bug => instead of showing a warning message, its window goes invisible and leads to the whole ui becoming unclickable (end the task from task manager) <br>
* Possible logic bugs related to the progress bar , sometimes it carries on progressing when you pressed stop => please raise an issue if this happens often <br>
* If the exe is getting flagged as a virus by your ac it is obviously a false positive, if you still do not trust it you can [compile/freeze](#freezing) it yourself <br>
* [VirusTotal](https://www.virustotal.com/gui/file/9df159098638ab8a8bec7205eeb271cb5891c19cdbb81bcd5368dfc1ef213f76/detection) <br>
### todos:
- [x] add a menu that shows you current installed mods/maps
- [x] fix the progress bar => progress bar logic based on an estimation
- [x] other improvements regarding the download (steamcmd likes to fail sometimes for no reason) => added a way to keep looping when steamcmd crashes and it will eventually finishes
- [x] add a queue window that you can slap in a bunch of items to download sequentially and or simultaneously
- [ ] add an option to login with your account => delayed (do we really need it?)
### Themes:
- If you choose "custom" theme a file called T7xwd_theme.json will be created in the current folder (where the exe at) , Don't add anything or edit any keyes just modify the colours, If you mess up something you can just rename the file and it'll go back to the default theme (you can always press custom again and the file will be created again, based on which theme you choose before)

View File

@ -1,5 +1,4 @@
import os
import shutil
import PyInstaller.__main__
from distutils.sysconfig import get_python_lib
@ -29,5 +28,7 @@ PyInstaller.__main__.run([
"--add-data", f"{site_packages_path}/CTkToolTip;CTkToolTip",
])
current_directory = os.path.dirname(__file__)
shutil.copy2(os.path.join(current_directory, "dist", "T7xWD.exe"), current_directory)
# create symbolic hardlink to main directory
if os.path.exists("T7xWD.exe"):
os.remove("T7xWD.exe")
os.link('dist/T7xWD.exe', 'T7xWD.exe')

Binary file not shown.

View File

@ -3,12 +3,13 @@ from src.imports import *
# Start helper functions
#testing app offline
# testing app offline
# import socket
# def guard(*args, **kwargs):
# pass
# socket.socket = guard
def check_config(name, fallback=None):
config = configparser.ConfigParser()
config.read(CONFIG_FILE_PATH)
@ -16,6 +17,7 @@ def check_config(name, fallback=None):
return config.get("Settings", name, fallback=fallback)
return config.get("Settings", name, fallback="")
def save_config(name, value):
config = configparser.ConfigParser()
config.read(CONFIG_FILE_PATH)
@ -24,25 +26,32 @@ def save_config(name, value):
with open(CONFIG_FILE_PATH, "w") as config_file:
config.write(config_file)
def check_custom_theme(theme_name):
if os.path.exists(os.path.join(APPLICATION_PATH, theme_name)):
return os.path.join(APPLICATION_PATH, theme_name)
else:
try: return os.path.join(RESOURCES_DIR, theme_name)
except: return os.path.join(RESOURCES_DIR, "T7xwd_theme.json")
try:
return os.path.join(RESOURCES_DIR, theme_name)
except:
return os.path.join(RESOURCES_DIR, "T7xwd_theme.json")
# theme initialization
ctk.set_appearance_mode(check_config("appearance", "Dark")) # Modes: "System" (standard), "Dark", "Light"
# Modes: "System" (standard), "Dark", "Light"
ctk.set_appearance_mode(check_config("appearance", "Dark"))
try:
ctk.set_default_color_theme(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")))
ctk.set_default_color_theme(check_custom_theme(
check_config("theme", fallback="T7xwd_theme.json")))
except:
save_config("theme", "T7xwd_theme.json")
ctk.set_default_color_theme(os.path.join(RESOURCES_DIR, "T7xwd_theme.json"))
ctk.set_default_color_theme(os.path.join(
RESOURCES_DIR, "T7xwd_theme.json"))
def get_latest_release_version():
try:
release_api_url = f"https://git.rimmyscorner.com/api/v1/repos/{GITHUB_REPO}/releases/latest"
release_api_url = f"https://api.github.com/repos/{GITHUB_REPO}/releases/latest"
response = requests.get(release_api_url)
response.raise_for_status()
data = response.json()
@ -51,6 +60,7 @@ def get_latest_release_version():
show_message("Warning", f"Error while checking for updates: \n{e}")
return None
def create_update_script(current_exe, new_exe, updater_folder, program_name):
script_content = f"""
@echo off
@ -76,6 +86,7 @@ def create_update_script(current_exe, new_exe, updater_folder, program_name):
return script_path
def if_internet_available(func):
if func == "return":
try:
@ -83,23 +94,28 @@ def if_internet_available(func):
return True
except:
return False
def wrapper(*args, **kwargs):
try:
requests.get("https://www.google.com", timeout=3)
return func(*args, **kwargs)
except:
show_message("Offline", "No internet connection. Please check your internet connection and try again.")
show_message(
"Offline", "No internet connection. Please check your internet connection and try again.")
return
return wrapper
@if_internet_available
def check_for_updates_func(window, ignore_up_todate=False):
try:
latest_version = get_latest_release_version()
current_version = VERSION
int_latest_version = int(latest_version.replace("v", "").replace(".", ""))
int_current_version = int(current_version.replace("v", "").replace(".", ""))
int_latest_version = int(
latest_version.replace("v", "").replace(".", ""))
int_current_version = int(
current_version.replace("v", "").replace(".", ""))
if latest_version and int_latest_version > int_current_version:
msg_box = CTkMessagebox(title="Update Available", message=f"An update is available! Install now?\n\nCurrent Version: {current_version}\nLatest Version: {latest_version}", option_1="View", option_2="No", option_3="Yes", fade_in_duration=int(1), sound=True)
@ -107,7 +123,8 @@ def check_for_updates_func(window, ignore_up_todate=False):
result = msg_box.get()
if result == "View":
webbrowser.open(f"https://git.rimmyscorner.com/{GITHUB_REPO}/releases/latest")
webbrowser.open(
f"https://github.com/{GITHUB_REPO}/releases/latest")
if result == "Yes":
from src.update_window import UpdateWindow
@ -125,15 +142,18 @@ def check_for_updates_func(window, ignore_up_todate=False):
elif int_latest_version == int_current_version:
if ignore_up_todate:
return
msg_box = CTkMessagebox(title="Up to Date!", message="No Updates Available!", option_1="Ok", sound=True)
msg_box = CTkMessagebox(
title="Up to Date!", message="No Updates Available!", option_1="Ok", sound=True)
result = msg_box.get()
else:
show_message("Error!", "An error occured while checking for updates!\nCheck your internet and try again")
show_message(
"Error!", "An error occured while checking for updates!\nCheck your internet and try again")
except Exception as e:
show_message("Error", f"Error while checking for updates: \n{e}", icon="cancel")
def extract_workshop_id(link):
try:
pattern = r'(?<=id=)(\d+)'
@ -146,6 +166,7 @@ def extract_workshop_id(link):
except:
return None
def check_steamcmd():
steamcmd_path = get_steamcmd_path()
steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe")
@ -155,25 +176,31 @@ def check_steamcmd():
return True
def initialize_steam(master):
try:
steamcmd_path = get_steamcmd_path()
steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe")
process = subprocess.Popen([steamcmd_exe_path, "+quit"], creationflags=subprocess.CREATE_NEW_CONSOLE)
process = subprocess.Popen(
[steamcmd_exe_path, "+quit"], creationflags=subprocess.CREATE_NEW_CONSOLE)
master.attributes('-alpha', 0.0)
process.wait()
if is_steamcmd_initialized():
show_message("SteamCMD has terminated!", "T7xWD is ready for action.", icon="info")
show_message("SteamCMD has terminated!",
"T7xWD is ready for action.", icon="info")
else:
show_message("SteamCMD has terminated!!", "SteamCMD isn't initialized yet")
show_message("SteamCMD has terminated!!",
"SteamCMD isn't initialized yet")
except:
show_message("Error!", "An error occurred please check your paths and try again.", icon="cancel")
show_message(
"Error!", "An error occurred please check your paths and try again.", icon="cancel")
master.attributes('-alpha', 1.0)
@if_internet_available
def valid_id(workshop_id):
data = item_steam_api(workshop_id)
if check_config("skip_invalid", "skip") == "no":
if check_config("skip_invalid", "on") == "off":
if data:
return True
if "consumer_app_id" in data['response']['publishedfiledetails'][0]:
@ -181,6 +208,7 @@ def valid_id(workshop_id):
else:
return False
def convert_speed(speed_bytes):
if speed_bytes < 1024:
return speed_bytes, "B/s"
@ -191,27 +219,31 @@ def convert_speed(speed_bytes):
else:
return speed_bytes / (1024 * 1024 * 1024), "GB/s"
def create_default_config():
config = configparser.ConfigParser()
config["Settings"] = {
"SteamCMDPath": APPLICATION_PATH,
"DestinationFolder": "",
"checkforupdtes": "on",
"checkforupdates": "on",
"console": "off"
}
with open(CONFIG_FILE_PATH, "w") as config_file:
config.write(config_file)
def get_steamcmd_path():
config = configparser.ConfigParser()
config.read(CONFIG_FILE_PATH)
return config.get("Settings", "SteamCMDPath", fallback=APPLICATION_PATH)
def extract_json_data(json_path, key):
with open(json_path, 'r') as json_file:
data = json.load(json_file)
return data.get(key, '')
def convert_bytes_to_readable(size_in_bytes, no_symb=None):
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if size_in_bytes < 1024.0:
@ -220,6 +252,7 @@ def convert_bytes_to_readable(size_in_bytes, no_symb=None):
return f"{size_in_bytes:.2f} {unit}"
size_in_bytes /= 1024.0
def get_workshop_file_size(workshop_id, raw=None):
url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}&searchtext="
response = requests.get(url)
@ -230,23 +263,34 @@ def get_workshop_file_size(workshop_id, raw=None):
if raw:
file_size_text = file_size_element.get_text(strip=True)
file_size_text = file_size_text.replace(",", "")
file_size_in_mb = float(file_size_text.replace(" MB", ""))
file_size_in_bytes = int(file_size_in_mb * 1024 * 1024)
if "GB" in file_size_text:
file_size_in_gb = float(file_size_text.replace(" GB", ""))
file_size_in_bytes = int(file_size_in_gb * 1024 * 1024 * 1024)
else:
file_size_in_mb = float(file_size_text.replace(" MB", ""))
file_size_in_bytes = int(file_size_in_mb * 1024 * 1024)
return convert_bytes_to_readable(file_size_in_bytes)
if file_size_element:
file_size_text = file_size_element.get_text(strip=True)
file_size_text = file_size_text.replace(",", "")
file_size_in_mb = float(file_size_text.replace(" MB", ""))
file_size_in_bytes = int(file_size_in_mb * 1024 * 1024)
if "GB" in file_size_text:
file_size_in_gb = float(file_size_text.replace(" GB", ""))
file_size_in_bytes = int(file_size_in_gb * 1024 * 1024 * 1024)
else:
file_size_in_mb = float(file_size_text.replace(" MB", ""))
file_size_in_bytes = int(file_size_in_mb * 1024 * 1024)
return file_size_in_bytes
return None
except:
except Exception as e:
print(e)
return None
def show_message(title, message, icon="warning", _return=False, option_1="No", option_2="Ok"):
if _return:
msg = CTkMessagebox(title=title, message=message, icon=icon, option_1=option_1, option_2=option_2, sound=True)
msg = CTkMessagebox(title=title, message=message, icon=icon,
option_1=option_1, option_2=option_2, sound=True)
response = msg.get()
if response == option_1:
return False
@ -259,18 +303,22 @@ def show_message(title, message, icon="warning", _return=False, option_1="No", o
CTkMessagebox(title=title, message=message, icon=icon, sound=True)
main_app.app.after(0, callback)
def launch_T7x_func(path):
procname = "t7x.exe"
def launch_game_func(path, procname="BlackOps3.exe", flags=""):
if not procname.endswith(('.exe', '.bat', '.sh', '.lnk')):
procname += '.exe'
try:
if procname in (p.name() for p in psutil.process_iter()):
for proc in psutil.process_iter():
if proc.name() == procname:
proc.kill()
T7x_path = os.path.join(path, procname)
subprocess.Popen([T7x_path ,"-launch"] , cwd=path)
show_message("Please wait!", "The game has launched in the background it will open up in a sec!", icon="info")
game_path = os.path.join(path, procname)
subprocess.Popen([game_path, flags], 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:
show_message("Error: Failed to launch T7x", f"Failed to launch t7x.exe\nMake sure to put in your correct T7x path\n{e}")
show_message(f"Error: Failed to launch game", f"Failed to start {procname}\n\nMake sure the game path is correct.\n\n{e}")
def remove_tree(folder_path, show_error=None):
if show_error:
@ -283,11 +331,13 @@ def remove_tree(folder_path, show_error=None):
except Exception as e:
pass
def convert_seconds(seconds):
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
return hours, minutes, seconds
def get_folder_size(folder_path):
total_size = 0
for path, dirs, files in os.walk(folder_path):
@ -296,6 +346,7 @@ def get_folder_size(folder_path):
total_size += os.stat(fp).st_size
return total_size
def is_steamcmd_initialized():
steamcmd_path = get_steamcmd_path()
steamcmd_exe_path = os.path.join(steamcmd_path, "steamcmd.exe")
@ -304,6 +355,7 @@ def is_steamcmd_initialized():
return False
return True
def get_button_state_colors(file_path, state):
try:
with open(file_path, 'r') as json_file:
@ -321,10 +373,12 @@ def get_button_state_colors(file_path, state):
except json.JSONDecodeError:
return None
def reset_steamcmd(no_warn=None):
steamcmd_path = get_steamcmd_path()
directories_to_reset = ["steamapps", "dumps", "logs", "depotcache", "appcache","userdata",]
directories_to_reset = ["steamapps", "dumps",
"logs", "depotcache", "appcache", "userdata",]
for directory in directories_to_reset:
directory_path = os.path.join(steamcmd_path, directory)
@ -338,10 +392,13 @@ def reset_steamcmd(no_warn=None):
os.remove(file_path)
if not no_warn:
show_message("Success!", "SteamCMD has been reset successfully!", icon="info")
show_message(
"Success!", "SteamCMD has been reset successfully!", icon="info")
else:
if not no_warn:
show_message("Warning!", "steamapps folder was not found, maybe already removed?", icon="warning")
show_message(
"Warning!", "steamapps folder was not found, maybe already removed?", icon="warning")
def get_item_name(id):
try:
@ -355,8 +412,52 @@ def get_item_name(id):
return False
# you gotta use my modded CTkToolTip originaly by Akascape
def show_noti(widget ,message, event=None, noti_dur=3.0, topmost=False):
CTkToolTip(widget, message=message, is_noti=True, noti_event=event, noti_dur=noti_dur, topmost=topmost)
def show_noti(widget, message, event=None, noti_dur=3.0, topmost=False):
CTkToolTip(widget, message=message, is_noti=True,
noti_event=event, noti_dur=noti_dur, topmost=topmost)
def get_update_time_from_html(workshop_id):
current_year = datetime.now().year
date_format_with_year = "%d %b, %Y @ %I:%M%p"
date_format_with_added_year = "%d %b @ %I:%M%p, %Y"
url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={workshop_id}"
try:
response = requests.get(url)
response.raise_for_status()
content = response.text
soup = BeautifulSoup(content, "html.parser")
details_stats_container = soup.find(
"div", class_="detailsStatsContainerRight")
if details_stats_container:
details_stat_elements = details_stats_container.find_all(
"div", class_="detailsStatRight")
try:
date_updated = details_stat_elements[2].text.strip()
except:
date_updated = None
if not date_updated:
return None
try:
date_updated = datetime.strptime(
date_updated, date_format_with_year)
except ValueError:
date_updated = datetime.strptime(
date_updated + f", {current_year}", date_format_with_added_year)
return int(date_updated.timestamp())
except Exception as e:
print(f"Error getting update time for URL {url}: {e}")
return None
def check_item_date(down_date, date_updated, format=False):
current_year = datetime.now().year
@ -364,15 +465,19 @@ def check_item_date(down_date, date_updated, format=False):
date_format_with_added_year = "%d %b @ %I:%M%p, %Y"
try:
try:
download_datetime = datetime.strptime(down_date, date_format_with_year)
download_datetime = datetime.strptime(
down_date, date_format_with_year)
except ValueError:
download_datetime = datetime.strptime(down_date + f", {current_year}", date_format_with_added_year)
download_datetime = datetime.strptime(
down_date + f", {current_year}", date_format_with_added_year)
if format:
try:
date_updated = datetime.strptime(date_updated, date_format_with_year)
date_updated = datetime.strptime(
date_updated, date_format_with_year)
except ValueError:
date_updated = datetime.strptime(date_updated + f", {current_year}", date_format_with_added_year)
date_updated = datetime.strptime(
date_updated + f", {current_year}", date_format_with_added_year)
if date_updated >= download_datetime:
return True
@ -381,6 +486,7 @@ def check_item_date(down_date, date_updated, format=False):
except:
return False
def get_window_size_from_registry():
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, REGISTRY_KEY_PATH, 0, winreg.KEY_READ) as key:
@ -392,16 +498,19 @@ def get_window_size_from_registry():
except (FileNotFoundError, OSError, ValueError, FileNotFoundError):
return None, None, None, None
def save_window_size_to_registry(width, height, x, y):
try:
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, REGISTRY_KEY_PATH) as key:
winreg.SetValueEx(key, "WindowWidth", 0, winreg.REG_SZ, str(width))
winreg.SetValueEx(key, "WindowHeight", 0, winreg.REG_SZ, str(height))
winreg.SetValueEx(key, "WindowHeight", 0,
winreg.REG_SZ, str(height))
winreg.SetValueEx(key, "WindowX", 0, winreg.REG_SZ, str(x))
winreg.SetValueEx(key, "WindowY", 0, winreg.REG_SZ, str(y))
except Exception as e:
print(f"Error saving to registry: {e}")
def item_steam_api(id):
try:
url = ITEM_INFO_API
@ -416,6 +525,7 @@ def item_steam_api(id):
print(e)
return False
def get_item_dates(ids):
try:
data = {
@ -429,12 +539,44 @@ def get_item_dates(ids):
if "response" in response_data:
item_details = response_data["response"]["publishedfiledetails"]
return {item["publishedfileid"]: item["time_updated"] for item in item_details}
return_list = {item["publishedfileid"]: item.get(
"time_updated", None) for item in item_details}
return return_list
return {}
except Exception as e:
print(e)
print("Error: could not fetch all update times. Breaking early.")
return {}
def isNullOrWhiteSpace(str):
if (str is None) or (str == "") or (str.isspace()):
return True
return False
def concatenate_sublists(a):
out = []
for sublist in a:
out.extend(sublist)
return out
def nextnonexistentdir(f, dir=os.path.dirname(os.path.realpath(__file__))):
i = 0
def already_exists(i):
if i==0 and os.path.exists(os.path.join(dir, (f).strip())):
return True
else:
return os.path.exists(os.path.join(dir, (f + f'_{str(i)}').strip()))
while already_exists(i):
i += 1
root_i_ext = [f, i]
return root_i_ext
# End helper functions

View File

@ -46,4 +46,4 @@ LIBRARY_FILE = "T7xwd_library.json"
RESOURCES_DIR = os.path.join(os.path.dirname(__file__), '..', 'resources')
UPDATER_FOLDER = "update"
REGISTRY_KEY_PATH = r"Software\T7xWD"
VERSION = "v0.3.3"
VERSION = "v0.3.5"

View File

@ -231,7 +231,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
item_type, item_name = item[5], item[0]
return (0, item_name) if item_type == "map" else (1, item_name)
def load_items(self, T7xFolder, dont_add=False):
def load_items(self, gameFolder, dont_add=False):
if self.refresh_next_time and not dont_add:
self.refresh_next_time = False
status = self.refresh_items()
@ -243,8 +243,8 @@ class LibraryTab(ctk.CTkScrollableFrame):
self.ids_added.clear()
self.refresh_next_time = True
maps_folder = Path(T7xFolder) / "mods"
mods_folder = Path(T7xFolder) / "usermaps"
maps_folder = Path(gameFolder) / "mods"
mods_folder = Path(gameFolder) / "usermaps"
mod_img = os.path.join(RESOURCES_DIR, "mod_image.png")
map_img = os.path.join(RESOURCES_DIR, "map_image.png")
b_mod_img = os.path.join(RESOURCES_DIR, "b_mod_image.png")
@ -262,14 +262,14 @@ class LibraryTab(ctk.CTkScrollableFrame):
except: pass
for folder_path in folders_to_process:
for zone_path in folder_path.glob("**/zone"):
for zone_path in folder_path.glob("*/zone"):
json_path = zone_path / "workshop.json"
if json_path.exists():
curr_folder_name = zone_path.parent.name
workshop_id = extract_json_data(json_path, "PublisherID") or "None"
name = re.sub(r'\^\d', '', extract_json_data(json_path, "Title")) or "None"
name = name[:45] + "..." if len(name) > 45 else name
name = name[:60] + "..." if len(name) > 60 else name
item_type = extract_json_data(json_path, "Type") or "None"
folder_name = extract_json_data(json_path, "FolderName") or "None"
folder_size_bytes = get_folder_size(zone_path.parent)
@ -298,7 +298,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
if curr_folder_name not in self.added_folders:
image_path = mod_img if item_type == "mod" else map_img
if not (str(curr_folder_name).strip() == str(workshop_id).strip() or str(curr_folder_name).strip() == str(folder_name).strip()
or str(curr_folder_name).strip() == f"{folder_name}_{workshop_id}"):
or str(curr_folder_name).strip() == f"{folder_name}_{workshop_id}" or str(curr_folder_name).strip() == f"{folder_name}_duplicated"):
try: self.remove_item_by_option(items_file, curr_folder_name, "folder_name")
except: pass
self.item_block_list.add(curr_folder_name)
@ -308,6 +308,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
elif (curr_folder_name not in self.added_folders and (workshop_id in self.ids_added or workshop_id == "None")):
try: self.remove_item_by_option(items_file, curr_folder_name, "folder_name")
except: pass
self.item_block_list.add(workshop_id)
text_to_add = re.sub(r'ID:\s+(?:\d+|None)', f'Folder: {curr_folder_name}', text_to_add)
image_path = b_mod_img if item_type == "mod" else b_map_img
text_to_add += " | ⚠️"
@ -325,7 +326,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
"folder_name": curr_folder_name,
"json_folder_name": folder_name
}
# when item is blocked ,item_exists_in_file() returns None for folder_found
# when item is blocked item_exists_in_file() returns None for folder_found
if not id_found and folder_found == None:
self.remove_item_by_option(items_file, curr_folder_name, "folder_name")
elif not id_found and not folder_found and curr_folder_name not in self.item_block_list and workshop_id not in self.ids_added:
@ -339,14 +340,14 @@ class LibraryTab(ctk.CTkScrollableFrame):
f.seek(0)
json.dump(items_data, f, indent=4)
if id_found and not folder_found and curr_folder_name not in self.item_block_list and workshop_id not in self.ids_added:
if curr_folder_name not in self.item_block_list:
self.update_or_add_item_by_id(items_file, item_info, workshop_id)
# keep here cuz of item_exists_in_file() testing
self.added_folders.add(curr_folder_name)
# added that cuz it sometimes can add blocked ids first
# and legit ids will be blocked cuz theyll be added to "ids_added"
if not workshop_id in self.ids_added and curr_folder_name not in self.item_block_list:
if not workshop_id in self.ids_added and curr_folder_name not in self.item_block_list and workshop_id!='None':
self.ids_added.add(workshop_id)
# sort items by type then alphabet
@ -354,7 +355,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
for item in ui_items_to_add:
self.add_item_helper(*item)
if not self.file_cleaned and os.path.exists(items_file):
if os.path.exists(items_file):
self.file_cleaned = True
self.clean_json_file(items_file)
@ -372,22 +373,22 @@ class LibraryTab(ctk.CTkScrollableFrame):
self.show_no_items_message(only_up=True)
return "No items in current selected folder"
def update_item(self, T7xFolder, id, item_type, foldername):
def update_item(self, gameFolder, id, item_type, foldername):
try:
if item_type == "map":
folder_path = Path(T7xFolder) / "usermaps" / f"{foldername}"
folder_path = Path(gameFolder) / "usermaps" / f"{foldername}"
elif item_type == "mod":
folder_path = Path(T7xFolder) / "mods" / f"{foldername}"
folder_path = Path(gameFolder) / "mods" / f"{foldername}"
else:
raise ValueError("Unsupported item_type. It must be 'map' or 'mod'.")
for zone_path in folder_path.glob("**/zone"):
for zone_path in folder_path.glob("*/zone"):
json_path = zone_path / "workshop.json"
if json_path.exists():
workshop_id = extract_json_data(json_path, "PublisherID")
if workshop_id == id:
name = extract_json_data(json_path, "Title").replace(">", "").replace("^", "")
name = name[:45] + "..." if len(name) > 45 else name
name = name[:60] + "..." if len(name) > 60 else name
item_type = extract_json_data(json_path, "Type")
folder_name = extract_json_data(json_path, "FolderName")
size = convert_bytes_to_readable(get_folder_size(zone_path.parent))
@ -456,7 +457,8 @@ class LibraryTab(ctk.CTkScrollableFrame):
self.added_items.clear()
self.added_folders.clear()
self.ids_added.clear()
status = self.load_items(main_app.app.edit_destination_folder.get().strip())
status = self.load_items( main_app.app.settings_tab.edit_destination_folder.get().strip())
main_app.app.title(f"T7x Workshop Downloader - Library ➜ {status}")
# main_app library event needs a return for status => when refresh_next_time is true
return status
@ -471,7 +473,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
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 T7x folder selected.")
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 game folder selected.")
def hide_no_items_message(self):
self.update_tooltip.configure(message="Check items for updates")
@ -587,7 +589,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
if json_path.exists():
workshop_id = extract_json_data(json_path, "PublisherID") or "None"
name = re.sub(r'\^\w+', '', extract_json_data(json_path, "Title")) or "None"
map_name = name[:45] + "..." if len(name) > 45 else name
map_name = name[:60] + "..." if len(name) > 60 else name
map_mod_type = extract_json_data(json_path, "Type") or "None"
preview_iamge = json_path.parent / "previewimage.png"
if preview_iamge.exists():
@ -693,7 +695,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
main_app.app.edit_workshop_id.delete(0, "end")
main_app.app.edit_workshop_id.insert(0, workshop_id)
main_app.app.main_button_event()
if invalid_warn and check_config("update_invalid", "no") == "yes":
if invalid_warn and check_config("update_invalid", "off") == "on":
main_app.app.download_map(update=True, invalid_item_folder=os.path.basename(folder))
else:
main_app.app.download_map(update=True)
@ -810,8 +812,8 @@ class LibraryTab(ctk.CTkScrollableFrame):
update_btn.configure(state="disabled")
update_btn_tooltip.configure(message="Currently offline")
view_button_tooltip.configure(message="Currently offline")
if check_config("update_invalid", "no") == "yes":
update_btn_tooltip.configure(message="update_invalid is set to 'yes' in config.ini")
if check_config("update_invalid", "off") == "on":
update_btn_tooltip.configure(message="update_invalid is set to 'on' in config.ini")
elif invalid_warn:
update_btn.configure(text="Update", state="disabled")
update_btn_tooltip.configure(message="Disabled due to item being blocked or duplicated")
@ -869,12 +871,19 @@ class LibraryTab(ctk.CTkScrollableFrame):
item_data = get_item_dates(item_ids)
for item_id, date_updated in item_data.items():
item_date = item_dates[item_id]
if not date_updated:
try:
new_date = get_update_time_from_html(item_id)
date_updated = new_date if new_date else 1
except:
date_updated = 1
item_date = item_dates[str(item_id)] if str(item_id) in item_dates else ""
date_updated = datetime.fromtimestamp(date_updated)
if check_item_date(item_date, date_updated):
date_updated = date_updated.strftime("%d %b @ %I:%M%p, %Y")
self.to_update.add(texts[item_id] + f" | Updated: {date_updated}")
if item_date != "":
date_updated = date_updated.strftime("%d %b @ %I:%M%p, %Y")
self.to_update.add(texts[item_id] + f" | Updated: {date_updated}")
except Exception as e:
show_message("Error", f"Error occurred\n{e}", icon="cancel")
@ -884,7 +893,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
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 T7x path!, you also need to have at least 1 item!")
show_message("Error checking for item updates! -> Setting is on", "Please visit library tab at least once with the correct game path! You also need to have at least 1 item!")
return
with open(os.path.join(APPLICATION_PATH, LIBRARY_FILE), 'r') as file:
@ -897,7 +906,7 @@ class LibraryTab(ctk.CTkScrollableFrame):
if_ids_need_update(item_ids, item_dates, texts)
except:
show_message("Error checking for item updates!", "Please visit the library tab at least once with the correct T7x path!, you also need to have at least 1 item!")
show_message("Error checking for item updates!", "Please visit the library tab at least once with the correct game path! You also need to have at least 1 item!")
return
check_for_update()

View File

@ -23,37 +23,40 @@ class T7xWD(ctk.CTk):
self.geometry(f"{920}x{560}")
self.minsize(920, 560)
ctk.set_window_scaling(float(check_config("scaling", 1.0)) + 0.25)
if os.path.exists(os.path.join(RESOURCES_DIR, "ryuk.ico")):
self.wm_iconbitmap(os.path.join(RESOURCES_DIR, "ryuk.ico"))
self.protocol("WM_DELETE_WINDOW", self.on_closing)
# Qeue frame/tab, keep here or app will start shrinked eveytime
self.qeueuframe = ctk.CTkFrame(self)
self.qeueuframe.columnconfigure(1, weight=1)
self.qeueuframe.columnconfigure(2, weight=1)
self.qeueuframe.columnconfigure(3, weight=1)
self.qeueuframe.rowconfigure(1, weight=1)
self.qeueuframe.rowconfigure(2, weight=1)
self.qeueuframe.rowconfigure(3, weight=1)
self.qeueuframe.rowconfigure(4, weight=1)
# Queue frame/tab, keep here or app will start shrinked eveytime
self.queue_tab_enabled = False # Enable Queue Tab
self.workshop_queue_label = ctk.CTkLabel(self.qeueuframe, text="Workshop IDs/Links -> press help to see examples:")
self.workshop_queue_label.grid(row=0, column=0, padx=(20, 20), pady=(20, 20), sticky="wns")
self.queueframe = ctk.CTkFrame(self)
self.queueframe.columnconfigure(1, weight=1)
self.queueframe.columnconfigure(2, weight=1)
self.queueframe.columnconfigure(3, weight=1)
self.queueframe.rowconfigure(1, weight=1)
self.queueframe.rowconfigure(2, weight=0)
self.queueframe.rowconfigure(3, weight=0)
self.queueframe.rowconfigure(4, weight=0)
self.help_button = ctk.CTkButton(master=self.qeueuframe, text="Help", command=self.help_queue_text_func, width=10, height=10, fg_color="#585858")
self.help_button.grid(row=0, column=0, padx=(352, 0), pady=(23, 0), sticky="en")
self.workshop_queue_label = ctk.CTkLabel(self.queueframe, text="Batch Downloader:")
self.workshop_queue_label.grid(row=0, column=0, padx=(20, 20), pady=(10, 10), sticky="wns")
self.help_button = ctk.CTkButton(master=self.queueframe, text="Help", command=self.help_queue_text_func, width=10, height=10, fg_color="#585858")
self.help_button.grid(row=0, column=3, padx=(0, 20), pady=(10, 0), sticky="en")
self.help_restore_content = None
self.queuetextarea = ctk.CTkTextbox(master=self.qeueuframe, font=("", 15))
self.queuetextarea.grid(row=1, column=0, columnspan=4, padx=(20, 20), pady=(0, 20), sticky="nwse")
self.queuetextarea = ctk.CTkTextbox(master=self.queueframe, font=("", 15))
self.queuetextarea.grid(row=1, column=0, columnspan=4, padx=(20, 20), pady=(0, 40), sticky="nwse")
self.status_text = ctk.CTkLabel(self.qeueuframe, text="Status: Standby!")
self.status_text.grid(row=3, column=0, padx=(20, 20), pady=(0, 20), sticky="ws")
self.status_text = ctk.CTkLabel(self.queueframe, text="Status: Standby!")
self.status_text.grid(row=1, column=0, padx=(20, 20), pady=(40, 0), sticky="ws")
self.skip_boutton = ctk.CTkButton(master=self.qeueuframe, text="Skip", command=self.skip_current_queue_item, width=10, height=10, fg_color="#585858")
self.skip_button = ctk.CTkButton(master=self.queueframe, text="Skip", command=self.skip_current_queue_item, width=10, height=10, fg_color="#585858")
self.qeueuframe.grid_remove()
self.queueframe.grid_remove()
# configure grid layout (4x4)
self.grid_columnconfigure(1, weight=1)
@ -78,18 +81,22 @@ class T7xWD(ctk.CTk):
self.txt_label.grid(row=1, column=0, padx=20, pady=(20, 10))
self.sidebar_main = ctk.CTkButton(self.sidebar_frame, height=28)
self.sidebar_main.grid(row=2, column=0, padx=10, pady=(20, 6))
self.sidebar_queue = ctk.CTkButton(self.sidebar_frame, height=28)
self.sidebar_queue.grid(row=3, column=0, padx=10, pady=6)
if self.queue_tab_enabled:
self.sidebar_queue = ctk.CTkButton(self.sidebar_frame, height=28)
self.sidebar_queue.grid(row=3, column=0, padx=10, pady=6)
self.sidebar_library = ctk.CTkButton(self.sidebar_frame, height=28)
self.sidebar_library.grid(row=4, column=0, padx=10, pady=6, sticky="n")
self.launch_game = ctk.CTkButton(self.sidebar_frame, text="Launch game", command=self.settings_launch_game, height=28)
self.launch_game.grid(row=5, column=0, padx=10, pady=6, sticky="n")
self.sidebar_settings = ctk.CTkButton(self.sidebar_frame, height=28)
self.sidebar_settings.grid(row=5, column=0, padx=10, pady=6, sticky="n")
self.sidebar_settings.grid(row=6, column=0, padx=10, pady=6, sticky="n")
# create optionsframe
self.optionsframe = ctk.CTkFrame(self)
self.optionsframe.grid(row=0, column=1, rowspan=2, padx=(0, 20), pady=(20, 0), sticky="nsew")
self.optionsframe.grid(row=0, column=1, rowspan=1, padx=(0, 20), pady=(20, 0), sticky="nsew")
self.txt_main = ctk.CTkLabel(self.optionsframe, text="💎 T7xWD 💎", font=(font, 20))
self.txt_main.grid(row=0, column=1, columnspan=5, padx=0, pady=(20, 20), sticky="n")
self.txt_main.grid(row=0, column=1, columnspan=1, padx=0, pady=(20, 20), sticky="n")
self.queueframe.grid(row=1, column=1, rowspan=1, padx=(0, 20), pady=(20, 0), sticky="nsew")
# create slider and progressbar frame
self.slider_progressbar_frame = ctk.CTkFrame(self)
@ -103,9 +110,6 @@ class T7xWD(ctk.CTk):
self.slider_progressbar_frame.rowconfigure(2, weight=1)
self.slider_progressbar_frame.rowconfigure(3, weight=1)
# self.spacer = ctk.CTkLabel(master=self.slider_progressbar_frame, text="")
# self.spacer.grid(row=0, column=0, columnspan=1)
self.label_speed = ctk.CTkLabel(master=self.slider_progressbar_frame, text="Awaiting Download!")
self.label_speed.grid(row=1, column=0, padx=20, pady=(0, 10), sticky="w")
self.elapsed_time = ctk.CTkLabel(master=self.slider_progressbar_frame, text="", anchor="center")
@ -127,79 +131,61 @@ class T7xWD(ctk.CTk):
# options frame
self.optionsframe.columnconfigure(1, weight=1)
self.optionsframe.columnconfigure(2, weight=1)
self.optionsframe.columnconfigure(3, weight=1)
self.optionsframe.rowconfigure(1, weight=1)
self.optionsframe.rowconfigure(2, weight=1)
self.optionsframe.rowconfigure(3, weight=1)
self.optionsframe.rowconfigure(4, weight=1)
self.optionsframe.columnconfigure(2, weight=0)
self.optionsframe.columnconfigure(3, weight=0)
self.optionsframe.rowconfigure(1, weight=0)
self.optionsframe.rowconfigure(2, weight=0)
self.optionsframe.rowconfigure(3, weight=0)
self.optionsframe.rowconfigure(4, weight=0)
self.label_workshop_id = ctk.CTkLabel(master=self.optionsframe, text="Enter the Workshop ID or Link of the map/mod you want to download:\n")
self.label_workshop_id.grid(row=1, column=1, padx=20, pady=(10, 0), columnspan=4, sticky="ws")
self.label_workshop_id = ctk.CTkLabel(master=self.optionsframe, text="Enter a Workshop ID or Link to download:\n")
self.label_workshop_id.grid(row=1, column=1, padx=(10, 5), pady=(10, 0), columnspan=1, sticky="ws")
self.check_if_changed = ctk.StringVar()
self.check_if_changed.trace_add("write", self.id_chnaged_handler)
self.check_if_changed.trace_add("write", self.id_changed_handler)
self.edit_workshop_id = ctk.CTkEntry(master=self.optionsframe, textvariable=self.check_if_changed)
self.edit_workshop_id.grid(row=2, column=1, padx=20, pady=(0, 10), columnspan=4, sticky="ewn")
self.edit_workshop_id.grid(row=2, column=1, padx=(5, 5), pady=(0, 10), columnspan=1, sticky="ewn")
self.button_browse = ctk.CTkButton(master=self.optionsframe, text="Workshop", command=self.open_browser, width=10)
self.button_browse.grid(row=2, column=5, padx=(0, 20), pady=(0, 10), sticky="en")
self.button_browse_tooltip = CTkToolTip(self.button_browse, message="Will open steam workshop for T7x in your browser")
self.button_browse.grid(row=2, column=2, padx=(5, 5), pady=(0, 10), sticky="en")
self.button_browse_tooltip = CTkToolTip(self.button_browse, message="Will open steam workshop in your browser")
self.info_button = ctk.CTkButton(master=self.optionsframe, text="Details", command=self.show_map_info, width=10)
self.info_button.grid(row=2, column=5, padx=(0, 20), pady=(0, 10), sticky="wn")
self.label_destination_folder = ctk.CTkLabel(master=self.optionsframe, text='Enter Your T7x folder:')
self.label_destination_folder.grid(row=3, column=1, padx=20, pady=(0, 0), columnspan=4, sticky="ws")
self.edit_destination_folder = ctk.CTkEntry(master=self.optionsframe, placeholder_text="Your T7x Instalation folder")
self.edit_destination_folder.grid(row=4, column=1, padx=20, pady=(0, 25), columnspan=4, sticky="ewn")
self.button_T7x_browse = ctk.CTkButton(master=self.optionsframe, text="Select", command=self.open_T7x_browser)
self.button_T7x_browse.grid(row=4, column=5, padx=(0, 20), pady=(0, 10), sticky="ewn")
self.label_steamcmd_path = ctk.CTkLabel(master=self.optionsframe, text="Enter SteamCMD path:")
self.label_steamcmd_path.grid(row=5, column=1, padx=20, pady=(0, 0), columnspan=3, sticky="wn")
self.edit_steamcmd_path = ctk.CTkEntry(master=self.optionsframe, placeholder_text="Enter your SteamCMD path")
self.edit_steamcmd_path.grid(row=6, column=1, padx=20, pady=(0, 30), columnspan=4, sticky="ewn")
self.button_steamcmd_browse = ctk.CTkButton(master=self.optionsframe, text="Select", command=self.open_steamcmd_path_browser)
self.button_steamcmd_browse.grid(row=6, column=5, padx=(0, 20), pady=(0, 30), sticky="ewn")
self.info_button.grid(row=2, column=3, padx=(5, 10), pady=(0, 10), sticky="wn")
# set default values
self.active_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "button_active_state_color")
self.normal_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "button_normal_state_color")
self.progress_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "progress_bar_fill_color")
self.settings_tab.appearance_mode_optionemenu.set("Dark")
self.settings_tab.scaling_optionemenu.set("100%")
self.settings_tab.scaling_optionemenu.set("80%")
self.progress_bar.set(0.0)
self.progress_bar.configure(progress_color=self.progress_color)
self.hide_settings_widgets()
self.button_stop.configure(state="disabled")
self.is_pressed = False
self.queue_enabled = False
self.queue_stop_button = False
self.is_downloading = False
self.is_steamcmd_updating = False
self.item_skipped = False
self.steam_updater_size = "Unknown size"
self.fail_threshold = 0
# sidebar windows bouttons
# sidebar windows buttons
self.sidebar_main.configure(command=self.main_button_event, text="Main ⬇️", fg_color=(self.active_color), state="active")
self.sidebar_library.configure(text="Library 📙", command=self.library_button_event)
self.sidebar_queue.configure(text="Queue 🚧", command=self.queue_button_event)
if self.queue_tab_enabled: self.sidebar_queue.configure(text="Queue 🚧", command=self.queue_button_event)
sidebar_settings_button_image = os.path.join(RESOURCES_DIR, "sett10.png")
self.sidebar_settings.configure(command=self.settings_button_event, text="", image=ctk.CTkImage(Image.open(sidebar_settings_button_image), size=(int(35), int(35))), fg_color="transparent", width=45, height=45)
self.sidebar_settings_tooltip = CTkToolTip(self.sidebar_settings, message="Settings")
self.sidebar_library_tooltip = CTkToolTip(self.sidebar_library, message="Experimental")
self.sidebar_queue_tooltip = CTkToolTip(self.sidebar_queue, message="Experimental")
if self.queue_tab_enabled: self.sidebar_queue_tooltip = CTkToolTip(self.sidebar_queue, message="Experimental")
self.bind("<Configure>", lambda e: self.save_window_size_position())
# context_menus
self.create_context_menu(self.edit_workshop_id)
self.create_context_menu(self.edit_destination_folder)
self.create_context_menu(self.edit_steamcmd_path)
self.create_context_menu(self.settings_tab.edit_destination_folder)
self.create_context_menu(self.settings_tab.edit_steamcmd_path)
self.create_context_menu(self.queuetextarea, textbox=True)
self.create_context_menu(self.library_tab.filter_entry, textbox=False, library=True)
# valid event required for filter_items()
@ -210,9 +196,10 @@ class T7xWD(ctk.CTk):
# load ui configs
self.load_configs()
if check_config("checkforupdtes") == "on":
if check_config("checkforupdates") == "on":
self.withdraw()
check_for_updates_func(self, ignore_up_todate=True)
try: check_for_updates_func(self, ignore_up_todate=True)
except: pass
self.update()
self.deiconify()
@ -331,12 +318,16 @@ class T7xWD(ctk.CTk):
print("Invalid geometry format:", geometry)
def on_closing(self):
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
save_config("DestinationFolder",self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath",self.settings_tab.edit_steamcmd_path.get())
startupexe = str(self.settings_tab.edit_startup_exe.get())
launchargs = str(self.settings_tab.edit_launch_args.get())
save_config("GameExecutable",startupexe if not isNullOrWhiteSpace(startupexe) else "BlackOps3")
save_config("LaunchParameters",launchargs if not isNullOrWhiteSpace(launchargs) else " ")
self.stop_download(on_close=True)
os._exit(0)
def id_chnaged_handler(self, some=None, other=None ,shit=None):
def id_changed_handler(self, some=None, other=None ,shit=None):
self.after(1, self.label_file_size.configure(text=f"File size: 0KB"))
def check_for_updates(self):
@ -349,16 +340,22 @@ class T7xWD(ctk.CTk):
def change_scaling_event(self, new_scaling: str):
new_scaling_float = int(new_scaling.replace("%", "")) / 100
ctk.set_widget_scaling(new_scaling_float)
ctk.set_window_scaling(new_scaling_float+0.25)
save_config("scaling", str(new_scaling_float))
def hide_main_widgets(self):
self.optionsframe.grid_forget()
self.slider_progressbar_frame.grid_forget()
self.hide_queue_widgets()
def use_queue_download(self):
return not self.queuetextarea.get("1.0", "end").isspace()
def show_main_widgets(self):
self.title("T7x Workshop Downloader - Main")
self.slider_progressbar_frame.grid(row=2, column=1, rowspan=1, padx=(0, 20), pady=(20, 20), sticky="nsew")
self.optionsframe.grid(row=0, column=1, rowspan=2, padx=(0, 20), pady=(20, 0), sticky="nsew")
self.optionsframe.grid(row=0, column=1, rowspan=1, padx=(0, 20), pady=(20, 0), sticky="nsew")
self.queueframe.grid(row=1, column=1, rowspan=1, padx=(0, 20), pady=(20, 0), sticky="nsew")
def hide_settings_widgets(self):
self.settings_tab.grid_forget()
@ -373,26 +370,24 @@ class T7xWD(ctk.CTk):
def show_library_widgets(self):
self.title("T7x Workshop Downloader - Library ➜ Loading... ⏳")
status = self.library_tab.load_items(self.edit_destination_folder.get())
status = self.library_tab.load_items(self.settings_tab.edit_destination_folder.get())
self.library_tab.grid(row=0, rowspan=3, column=1, padx=(0, 20), pady=(20, 20), sticky="nsew")
self.title(f"T7x Workshop Downloader - Library ➜ {status}")
def show_queue_widgets(self):
self.title("T7x Workshop Downloader - Queue")
self.optionsframe.grid_forget()
self.queue_enabled = True
self.slider_progressbar_frame.grid(row=2, column=1, rowspan=1, padx=(0, 20), pady=(20, 20), sticky="nsew")
self.qeueuframe.grid(row=0, column=1, rowspan=2, padx=(0, 20), pady=(20, 0), sticky="nsew")
self.queueframe.grid(row=0, column=1, rowspan=2, padx=(0, 20), pady=(20, 0), sticky="nsew")
def hide_queue_widgets(self):
self.queue_enabled = False
self.qeueuframe.grid_forget()
self.queueframe.grid_forget()
def main_button_event(self):
self.sidebar_main.configure(state="active", fg_color=(self.active_color))
self.sidebar_settings.configure(state="normal", fg_color="transparent")
self.sidebar_library.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color))
if self.queue_tab_enabled: self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color))
self.hide_settings_widgets()
self.hide_library_widgets()
self.hide_queue_widgets()
@ -401,7 +396,7 @@ class T7xWD(ctk.CTk):
def settings_button_event(self):
self.sidebar_main.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_library.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color))
if self.queue_tab_enabled: self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_settings.configure(state="active", fg_color=(self.active_color))
self.hide_main_widgets()
self.hide_library_widgets()
@ -411,7 +406,7 @@ class T7xWD(ctk.CTk):
def library_button_event(self):
self.sidebar_main.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_settings.configure(state="normal", fg_color="transparent")
self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color))
if self.queue_tab_enabled: self.sidebar_queue.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_library.configure(state="active", fg_color=(self.active_color))
self.hide_main_widgets()
self.hide_settings_widgets()
@ -422,7 +417,7 @@ class T7xWD(ctk.CTk):
self.sidebar_main.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_settings.configure(state="normal", fg_color="transparent")
self.sidebar_library.configure(state="normal", fg_color=(self.normal_color))
self.sidebar_queue.configure(state="active", fg_color=(self.active_color))
if self.queue_tab_enabled: self.sidebar_queue.configure(state="active", fg_color=(self.active_color))
self.hide_settings_widgets()
self.hide_library_widgets()
self.show_queue_widgets()
@ -431,12 +426,19 @@ class T7xWD(ctk.CTk):
if os.path.exists(CONFIG_FILE_PATH):
destination_folder = check_config("DestinationFolder", "")
steamcmd_path = check_config("SteamCMDPath", APPLICATION_PATH)
startup_exe = check_config("GameExecutable", "BlackOps3")
launch_params = check_config("LaunchParameters", None)
new_appearance_mode = check_config("appearance", "Dark")
new_scaling = check_config("scaling", 1.0)
self.edit_destination_folder.delete(0, "end")
self.edit_destination_folder.insert(0, destination_folder)
self.edit_steamcmd_path.delete(0, "end")
self.edit_steamcmd_path.insert(0, steamcmd_path)
self.settings_tab.edit_destination_folder.delete(0, "end")
self.settings_tab.edit_destination_folder.insert(0, destination_folder)
self.settings_tab.edit_steamcmd_path.delete(0, "end")
self.settings_tab.edit_steamcmd_path.insert(0, steamcmd_path)
self.settings_tab.edit_startup_exe.delete(0, "end")
self.settings_tab.edit_startup_exe.insert(0, startup_exe)
if not isNullOrWhiteSpace(launch_params):
self.settings_tab.edit_launch_args.delete(0, "end")
self.settings_tab.edit_launch_args.insert(0, launch_params)
ctk.set_appearance_mode(new_appearance_mode)
ctk.set_widget_scaling(float(new_scaling))
self.settings_tab.appearance_mode_optionemenu.set(new_appearance_mode)
@ -452,18 +454,20 @@ class T7xWD(ctk.CTk):
scaling_float = float(new_scaling)*100
scaling_int = math.trunc(scaling_float)
self.settings_tab.scaling_optionemenu.set(f"{scaling_int}%")
self.edit_steamcmd_path.delete(0, "end")
self.edit_steamcmd_path.insert(0, APPLICATION_PATH)
self.settings_tab.edit_steamcmd_path.delete(0, "end")
self.settings_tab.edit_steamcmd_path.insert(0, APPLICATION_PATH)
self.settings_tab.edit_startup_exe.delete(0, "end")
self.settings_tab.edit_startup_exe.insert(0, "BlackOps3")
create_default_config()
def help_queue_text_func(self, event=None):
textarea_content = self.queuetextarea.get("1.0", "end").strip()
help_text = "3010399939,2976006537,2118338989,...\nor:\n3010399939\n2976006537\n2113146805\n..."
help_text = "3010399939,2976006537,2118338989 \n\nor:\n\n3010399939\n2976006537\n2113146805"
if any(char.strip() for char in textarea_content):
if help_text in textarea_content:
self.workshop_queue_label.configure(text="Workshop IDs/Links => press help to see examples:")
self.workshop_queue_label.configure(text="Batch Downloader:")
self.help_button.configure(text="Help")
self.queuetextarea.configure(state="normal")
self.queuetextarea.configure(state="normal",text_color="#fcfcfc")
self.queuetextarea.delete(1.0, "end")
self.queuetextarea.insert(1.0, "")
if self.help_restore_content:
@ -472,37 +476,37 @@ class T7xWD(ctk.CTk):
self.queuetextarea.insert(1.0, "")
else:
if not help_text in textarea_content:
self.help_restore_content = textarea_content
self.workshop_queue_label.configure(text="Workshop IDs/Links => press help to see examples:")
self.help_restore_content = textarea_content
self.workshop_queue_label.configure(text="Batch Downloader:")
self.help_button.configure(text="Restore")
self.queuetextarea.configure(state="normal")
self.queuetextarea.configure(state="normal",text_color="#fcfcfc")
self.queuetextarea.delete(1.0, "end")
self.queuetextarea.insert(1.0, "")
self.workshop_queue_label.configure(text="Workshop IDs/Links => press restore to remove examples:")
self.workshop_queue_label.configure(text="Batch Downloader Example:")
self.queuetextarea.insert(1.0, help_text)
self.queuetextarea.configure(state="disabled")
self.queuetextarea.configure(state="disabled", text_color="gray74")
else:
self.help_restore_content = textarea_content
self.workshop_queue_label.configure(text="Workshop IDs/Links => press restore to remove examples:")
self.workshop_queue_label.configure(text="Batch Downloader Example:")
self.help_button.configure(text="Restore")
self.queuetextarea.insert(1.0, help_text)
self.queuetextarea.configure(state="disabled")
self.queuetextarea.configure(state="disabled", text_color="gray74")
def open_T7x_browser(self):
selected_folder = ctk.filedialog.askdirectory(title="Select T7x Folder")
selected_folder = ctk.filedialog.askdirectory(title="Select Game Folder")
if selected_folder:
self.edit_destination_folder.delete(0, "end")
self.edit_destination_folder.insert(0, selected_folder)
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
self.settings_tab.edit_destination_folder.delete(0, "end")
self.settings_tab.edit_destination_folder.insert(0, selected_folder)
save_config("DestinationFolder" ,self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.settings_tab.edit_steamcmd_path.get())
def open_steamcmd_path_browser(self):
selected_folder = ctk.filedialog.askdirectory(title="Select SteamCMD Folder")
if selected_folder:
self.edit_steamcmd_path.delete(0, "end")
self.edit_steamcmd_path.insert(0, selected_folder)
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
self.settings_tab.edit_steamcmd_path.delete(0, "end")
self.settings_tab.edit_steamcmd_path.insert(0, selected_folder)
save_config("DestinationFolder" ,self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.settings_tab.edit_steamcmd_path.get())
def show_steam_warning_message(self):
def callback():
@ -519,12 +523,15 @@ class T7xWD(ctk.CTk):
link = "https://steamcommunity.com/app/311210/workshop/"
webbrowser.open(link)
def settings_launch_game(self):
launch_game_func(check_config("destinationfolder"), self.settings_tab.edit_startup_exe.get(), self.settings_tab.edit_launch_args.get())
@if_internet_available
def download_steamcmd(self):
self.edit_steamcmd_path.delete(0, "end")
self.edit_steamcmd_path.insert(0, APPLICATION_PATH)
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
self.settings_tab.edit_steamcmd_path.delete(0, "end")
self.settings_tab.edit_steamcmd_path.insert(0, APPLICATION_PATH)
save_config("DestinationFolder" ,self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.settings_tab.edit_steamcmd_path.get())
steamcmd_url = "https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip"
steamcmd_zip_path = os.path.join(APPLICATION_PATH, "steamcmd.zip")
@ -795,6 +802,61 @@ class T7xWD(ctk.CTk):
self.after(0, main_thread)
def check_if_steamcmd_updating(self, log_file_path):
temp_file_path = log_file_path + '.temp'
if not os.path.exists(log_file_path):
if os.path.isdir(log_file_path):
try: os.makedirs(log_file_path)
except: return False
else:
try:
with open(log_file_path, 'w') as file:
file.write('')
except: pass
try: shutil.copy2(log_file_path, temp_file_path)
except: return False
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 "downloading update" in line:
print(line)
match = re.search(r'(\d{1,3}(?:,\d{3})* of \d{1,3}(?:,\d{3})* [kKMGTP]{0,1}B)', _line)
if match:
update_size_str = match.group(1)
self.steam_updater_size = update_size_str
return True
elif self.is_steamcmd_updating and "downloading update" not in line:
self.steam_updater_size = "Installing"
return False
except:
try:
os.remove(temp_file_path)
except:
return False
finally:
try: os.remove(temp_file_path)
except: pass
def check_steamcmd_stdout(self, log_file_path, target_item_id):
temp_file_path = log_file_path + '.temp'
if not os.path.exists(log_file_path):
@ -846,7 +908,7 @@ class T7xWD(ctk.CTk):
def skip_current_queue_item(self):
if self.button_download._state == "normal":
self.skip_boutton.grid_remove()
self.skip_button.grid_remove()
self.after(1, self.status_text.configure(text=f"Status: Standby!"))
return
self.settings_tab.stopped = True
@ -858,7 +920,7 @@ class T7xWD(ctk.CTk):
subprocess.run(['taskkill', '/F', '/IM', 'steamcmd.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
creationflags=subprocess.CREATE_NO_WINDOW)
self.skip_boutton.grid_remove()
self.skip_button.grid_remove()
self.after(2, self.status_text.configure(text=f"Status: Skipping..."))
self.label_speed.configure(text="Network Speed: 0 KB/s")
self.progress_text.configure(text="0%")
@ -867,6 +929,7 @@ class T7xWD(ctk.CTk):
# the real deal
def run_steamcmd_command(self, command, map_folder, wsid, queue=None):
steamcmd_path = get_steamcmd_path()
steamcmd_bootstrap_logs = os.path.join(steamcmd_path, "logs", "bootstrap_log.txt")
stdout_path = os.path.join(steamcmd_path, "logs", "workshop_log.txt")
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
@ -874,9 +937,13 @@ class T7xWD(ctk.CTk):
except: pass
try:
with open(steamcmd_bootstrap_logs, 'w') as file:
file.write('')
with open(stdout_path, 'w') as file:
file.write('')
except:
try: os.rename(stdout_path, os.path.join(map_folder, os.path.join(steamcmd_bootstrap_logs, f"bootstrap_log_couldntremove_{timestamp}.txt")))
except: pass
try: os.rename(stdout_path, os.path.join(map_folder, os.path.join(stdout_path, f"workshop_log_couldntremove_{timestamp}.txt")))
except: pass
@ -918,14 +985,18 @@ class T7xWD(ctk.CTk):
if process.poll() is not None:
break
if not self.is_downloading:
if self.check_if_steamcmd_updating(steamcmd_bootstrap_logs):
self.is_steamcmd_updating = True
if self.check_steamcmd_stdout(stdout_path, wsid):
start_time = time.time()
self.is_downloading = True
self.is_steamcmd_updating = False
elapsed_time = time.time() - start_time
time.sleep(1)
# print("Broken freeeee!")
self.is_downloading = False
self.is_steamcmd_updating = False
try:
with open(stdout_path, 'w') as file:
file.write('')
@ -968,6 +1039,8 @@ class T7xWD(ctk.CTk):
if process.poll() is not None:
break
if not self.is_downloading:
if self.check_if_steamcmd_updating(steamcmd_bootstrap_logs):
self.is_steamcmd_updating = True
if self.check_steamcmd_stdout(stdout_path, wsid):
start_time = time.time()
self.is_downloading = True
@ -976,6 +1049,7 @@ class T7xWD(ctk.CTk):
# print("Broken freeeee!")
self.is_downloading = False
self.is_steamcmd_updating = False
try:
with open(stdout_path, 'w') as file:
file.write('')
@ -1011,7 +1085,7 @@ class T7xWD(ctk.CTk):
msg = CTkMessagebox(title="Downloads Complete", message=message, icon="info", option_1="Launch", option_2="Ok", sound=True)
response = msg.get()
if response=="Launch":
launch_T7x_func(self.edit_destination_folder.get().strip())
launch_game_func(self.settings_tab.edit_destination_folder.get().strip(), self.settings_tab.edit_startup_exe.get(), self.settings_tab.edit_launch_args.get())
if response=="Ok":
return
self.after(0, callback)
@ -1023,8 +1097,8 @@ class T7xWD(ctk.CTk):
if not self.is_pressed:
self.after(1, self.label_speed.configure(text=f"Loading..."))
self.is_pressed = True
self.library_tab.load_items(self.edit_destination_folder.get(), dont_add=True)
if self.queue_enabled:
self.library_tab.load_items(self.settings_tab.edit_destination_folder.get(), dont_add=True)
if self.use_queue_download():
self.item_skipped = False
start_down_thread = threading.Thread(target=self.queue_download_thread, args=(update,))
start_down_thread.start()
@ -1038,8 +1112,8 @@ class T7xWD(ctk.CTk):
self.stopped = False
self.queue_stop_button = False
try:
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
save_config("DestinationFolder" ,self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.settings_tab.edit_steamcmd_path.get())
if not check_steamcmd():
self.show_steam_warning_message()
@ -1065,7 +1139,7 @@ class T7xWD(ctk.CTk):
self.stop_download()
return
destination_folder = self.edit_destination_folder.get().strip()
destination_folder = self.settings_tab.edit_destination_folder.get().strip()
if not destination_folder or not os.path.exists(destination_folder):
show_message("Error", "Please select a valid destination folder => in the main tab!.")
@ -1102,13 +1176,16 @@ class T7xWD(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:
show_message("Error", "Failed to retrieve file size.", icon="cancel")
self.stop_download()
return
ws_file_size = 1
file_size = 1
show_message("Error", "Failed to retrieve file size, Continuing anyway", icon="cancel")
# self.stop_download()
# return
items_ws_sizes[workshop_id] = ws_file_size
self.total_queue_size += ws_file_size
if any(workshop_id in item for item in self.library_tab.added_items):
self.already_installed.append(workshop_id)
@ -1160,7 +1237,7 @@ class T7xWD(ctk.CTk):
self.stop_download()
return
ws_file_size = get_workshop_file_size(workshop_id)
file_size = ws_file_size
file_size = ws_file_size if ws_file_size is not None else 1
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)
map_folder = os.path.join(get_steamcmd_path(), "steamapps", "workshop", "content", "311210", workshop_id)
@ -1170,7 +1247,7 @@ class T7xWD(ctk.CTk):
def check_and_update_progress():
previous_net_speed = 0
est_downloaded_bytes = 0
file_size = ws_file_size
file_size = ws_file_size if ws_file_size is not None else 1
item_name = get_item_name(workshop_id) if get_item_name(workshop_id) else "Error getting name"
while not self.settings_tab.stopped:
@ -1196,7 +1273,10 @@ class T7xWD(ctk.CTk):
self.item_skipped = False
while not self.is_downloading and not self.settings_tab.stopped:
self.after(1, self.label_speed.configure(text=f"Waiting for steamcmd..."))
if self.is_steamcmd_updating:
self.after(1, self.label_speed.configure(text=f"Updating steamcmd ({self.steam_updater_size})..."))
else:
self.after(1, self.label_speed.configure(text=f"Waiting for steamcmd..."))
time_elapsed = time.time() - start_time
elapsed_hours, elapsed_minutes, elapsed_seconds = convert_seconds(time_elapsed)
if self.settings_tab.show_fails:
@ -1206,11 +1286,12 @@ class T7xWD(ctk.CTk):
self.after(1, self.status_text.configure(
text=f"Status: Total size: ~{convert_bytes_to_readable(self.total_queue_size)} | ID: {workshop_id} | {item_name} | Waiting {current_number}/{total_items}"))
if len(items) > 1:
self.skip_boutton.grid(row=3, column=1, padx=(10, 20), pady=(0, 25), sticky="ws")
self.skip_button.grid(row=1, column=3, padx=(0, 20), pady=(0, 8), sticky="es")
if index == len(items) - 1:
self.skip_boutton.grid_remove()
self.skip_button.grid_remove()
time.sleep(1)
if self.is_downloading:
self.is_steamcmd_updating = False
break
try:
@ -1355,7 +1436,7 @@ class T7xWD(ctk.CTk):
remove_tree(map_folder)
remove_tree(download_folder)
self.library_tab.update_item(self.edit_destination_folder.get(), workshop_id, mod_type, folder_name)
self.library_tab.update_item(self.settings_tab.edit_destination_folder.get(), workshop_id, mod_type, folder_name)
if index == len(items) - 1:
self.after(1, self.status_text.configure(text=f"Status: Done! => Please press stop only if you see no popup window (rare bug)"))
@ -1378,7 +1459,7 @@ class T7xWD(ctk.CTk):
self.button_download.configure(state="normal")
self.button_stop.configure(state="disabled")
self.after(1, self.status_text.configure(text=f"Status: Done!"))
self.skip_boutton.grid_remove()
self.skip_button.grid_remove()
self.after(1, self.label_file_size.configure(text=f"File size: 0KB"))
self.settings_tab.stopped = True
self.stop_download()
@ -1393,8 +1474,8 @@ class T7xWD(ctk.CTk):
try:
self.settings_tab.stopped = False
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
save_config("DestinationFolder" ,self.settings_tab.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.settings_tab.edit_steamcmd_path.get())
if not check_steamcmd():
self.show_steam_warning_message()
@ -1408,7 +1489,7 @@ class T7xWD(ctk.CTk):
workshop_id = self.edit_workshop_id.get().strip()
destination_folder = self.edit_destination_folder.get().strip()
destination_folder = self.settings_tab.edit_destination_folder.get().strip()
if not destination_folder or not os.path.exists(destination_folder):
show_message("Error", "Please select a valid destination folder.")
@ -1437,14 +1518,16 @@ class T7xWD(ctk.CTk):
file_size = ws_file_size
if not valid_id(workshop_id):
show_message("Warning", "Please enter a valid Workshop ID/Link.", icon="warning")
show_message("Warning", "Invalid Workshop ID/Link.\nYou can attempt to update with 'Skip Invalid Items' disabled.", icon="warning")
self.stop_download()
return
if file_size is None:
show_message("Error", "Failed to retrieve file size.", icon="cancel")
self.stop_download()
return
ws_file_size = 1
file_size = 1
show_message("Error", "Failed to retrieve file size, Continuing anyway", icon="cancel")
# self.stop_download()
# return
if not update:
if any(workshop_id in item for item in self.library_tab.added_items):
@ -1464,7 +1547,7 @@ class T7xWD(ctk.CTk):
previous_net_speed = 0
est_downloaded_bytes = 0
start_time = time.time()
file_size = ws_file_size
file_size = ws_file_size if ws_file_size is not None else 1
while not self.settings_tab.stopped:
if self.settings_tab.steamcmd_reset:
@ -1473,7 +1556,10 @@ class T7xWD(ctk.CTk):
est_downloaded_bytes = 0
while not self.is_downloading and not self.settings_tab.stopped:
self.after(1, self.label_speed.configure(text=f"Waiting for steamcmd..."))
if self.is_steamcmd_updating:
self.after(1, self.label_speed.configure(text=f"Updating steamcmd ({self.steam_updater_size})..."))
else:
self.after(1, self.label_speed.configure(text=f"Waiting for steamcmd..."))
time_elapsed = time.time() - start_time
elapsed_hours, elapsed_minutes, elapsed_seconds = convert_seconds(time_elapsed)
if self.settings_tab.show_fails:
@ -1482,6 +1568,7 @@ class T7xWD(ctk.CTk):
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:
self.is_steamcmd_updating = False
break
try:
@ -1626,7 +1713,7 @@ class T7xWD(ctk.CTk):
remove_tree(download_folder)
if not invalid_item_folder:
self.library_tab.update_item(self.edit_destination_folder.get(), workshop_id, mod_type, folder_name)
self.library_tab.update_item(self.settings_tab.edit_destination_folder.get(), workshop_id, mod_type, folder_name)
self.show_complete_message(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)")
self.button_download.configure(state="normal")
self.button_stop.configure(state="disabled")
@ -1692,4 +1779,4 @@ class T7xWD(ctk.CTk):
self.progress_bar.set(0.0)
self.after(50, self.status_text.configure(text=f"Status: Standby!"))
self.after(1, self.label_speed.configure(text=f"Awaiting Download!"))
self.skip_boutton.grid_remove()
self.skip_button.grid_remove()

View File

@ -28,13 +28,17 @@ class SettingsTab(ctk.CTkFrame):
self.grid_columnconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
left_frame = ctk.CTkFrame(self)
left_frame.grid(row=0, column=0, padx=(20, 20), pady=(20, 0), sticky="nsew")
left_frame.grid(row=0, column=0, columnspan=2, padx=(20, 20), pady=(20, 0), sticky="nsew")
left_frame.grid_columnconfigure(1, weight=1)
right_frame = ctk.CTkFrame(self)
right_frame.grid(row=0, column=1, padx=(20, 20), pady=(20, 0), sticky="nsew")
right_frame.grid(row=0, column=2, padx=(0, 20), pady=(20, 0), sticky="nsew")
right_frame.grid_columnconfigure(1, weight=1)
self.update_idletasks()
# Save button
self.save_button = ctk.CTkButton(self, text="Save", command=self.save_settings, state='disabled')
self.save_button.grid(row=3, column=1, padx=40, pady=(20, 20), sticky="ne")
# Check for updates checkbox
self.check_updates_var = ctk.BooleanVar()
self.check_updates_var.trace_add("write", self.enable_save_button)
@ -45,7 +49,7 @@ class SettingsTab(ctk.CTkFrame):
# Show console checkbox
self.console_var = ctk.BooleanVar()
self.console_var.trace_add("write", self.enable_save_button)
self.checkbox_show_console = ctk.CTkSwitch(left_frame, text="Console (On Download)", variable=self.console_var)
self.checkbox_show_console = ctk.CTkSwitch(left_frame, text="Display SteamCMD console", variable=self.console_var)
self.checkbox_show_console.grid(row=1, column=1, padx=20, pady=(20, 0), sticky="nw")
self.checkbox_show_console_tooltip = CTkToolTip(self.checkbox_show_console, message="Toggle SteamCMD console\nPlease don't close the Console If you want to stop press the Stop button")
self.console_var.set(self.load_settings("console"))
@ -53,7 +57,7 @@ class SettingsTab(ctk.CTkFrame):
# Show continuous checkbox
self.continuous_var = ctk.BooleanVar()
self.continuous_var.trace_add("write", self.enable_save_button)
self.checkbox_continuous = ctk.CTkSwitch(left_frame, text="Continuous Download", variable=self.continuous_var)
self.checkbox_continuous = ctk.CTkSwitch(left_frame, text="Continuous download", variable=self.continuous_var)
self.checkbox_continuous.grid(row=2, column=1, padx=20, pady=(20, 0), sticky="nw")
self.checkbox_continuous_tooltip = CTkToolTip(self.checkbox_continuous, message="This will make sure that the download restarts and resumes! until it finishes if steamcmd crashes randomly (it will not redownload from the start)")
self.continuous_var.set(self.load_settings("continuous_download"))
@ -69,16 +73,16 @@ class SettingsTab(ctk.CTkFrame):
# Show estimated_progress checkbox
self.estimated_progress_var = ctk.BooleanVar()
self.estimated_progress_var.trace_add("write", self.enable_save_button)
self.estimated_progress_cb = 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_cb.grid(row=4, column=1, padx=20, pady=(20, 0), sticky="nw")
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")
self.estimated_progress_var.set(self.load_settings("estimated_progress", "on"))
# Show show fails checkbox
# Show fails checkbox
self.show_fails_var = ctk.BooleanVar()
self.show_fails_var.trace_add("write", self.enable_save_button)
self.show_fails_cb = 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", variable=self.show_fails_var)
self.show_fails_cb.grid(row=5, column=1, padx=20, pady=(20, 0), sticky="nw")
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"))
@ -94,75 +98,138 @@ class SettingsTab(ctk.CTkFrame):
# check items for update on launch
self.check_items_var = ctk.BooleanVar()
self.check_items_var.trace_add("write", self.enable_save_button)
self.check_items_ch = ctk.CTkSwitch(left_frame, text="Check Library items on launch", variable=self.check_items_var)
self.check_items_ch = ctk.CTkSwitch(left_frame, text="Check library items on launch", variable=self.check_items_var)
self.check_items_ch.grid(row=7, column=1, padx=20, pady=(20, 0), sticky="nw")
self.check_items_tooltip = CTkToolTip(self.check_items_ch, message="This will show a window on launch of items that have pending updates -> you can open it manually from library tab")
self.check_items_var.set(self.load_settings("check_items", "off"))
# Resetr steam on many fails
# TODO: get windows size to padx for the following checkboxes
# update invalid
self.invalid_items_var = ctk.BooleanVar()
self.invalid_items_var.trace_add("write", self.enable_save_button)
self.invalid_items_ch = ctk.CTkSwitch(left_frame, text="Update invalid items", variable=self.invalid_items_var)
self.invalid_items_ch.grid(row=0, column=1, padx=(300,0), pady=(20, 0), sticky="nw")
self.invalid_items_tooltip = CTkToolTip(self.invalid_items_ch, message="Allow updating invalid items from the details tab")
self.invalid_items_var.set(self.load_settings("update_invalid", "off"))
# skip invalid
self.skip_items_var = ctk.BooleanVar()
self.skip_items_var.trace_add("write", self.enable_save_button)
self.skip_items_ch = ctk.CTkSwitch(left_frame, text="Skip invalid items", variable=self.skip_items_var)
self.skip_items_ch.grid(row=1, column=1, padx=(300,0), pady=(20, 0), sticky="nw")
self.skip_items_tooltip = CTkToolTip(self.skip_items_ch, message="Skip invalid items")
self.skip_items_var.set(self.load_settings("skip_invalid", "off"))
# text input fields
self.label_destination_folder = ctk.CTkLabel(left_frame, text='Enter Game folder:')
self.label_destination_folder.grid(row=8, column=1, padx=20, pady=(20, 0), columnspan=1, sticky="ws")
self.entry_var1 = ctk.StringVar(value="")
self.edit_destination_folder = ctk.CTkEntry(left_frame, placeholder_text="game installation folder", textvariable=self.entry_var1)
self.edit_destination_folder.grid(row=9, column=1, padx=20, pady=(0, 10), columnspan=1, sticky="ewn")
self.entry_var1.trace_add("write", self.enable_save_button)
self.button_T7x_browse = ctk.CTkButton(left_frame, text="Select", command=self.open_T7x_browser)
self.button_T7x_browse.grid(row=9, column=2, padx=(0, 20), pady=(0, 10), sticky="ewn")
self.label_steamcmd_path = ctk.CTkLabel(left_frame, text="Enter SteamCMD path:")
self.label_steamcmd_path.grid(row=10, column=1, padx=20, pady=(0, 0), columnspan=1, sticky="wn")
self.entry_var2 = ctk.StringVar(value="")
self.edit_steamcmd_path = ctk.CTkEntry(left_frame, placeholder_text="Enter SteamCMD path", textvariable=self.entry_var2)
self.edit_steamcmd_path.grid(row=11, column=1, padx=20, pady=(0, 10), columnspan=1, sticky="ewn")
self.entry_var2.trace_add("write", self.enable_save_button)
self.button_steamcmd_browse = ctk.CTkButton(left_frame, text="Select", command=self.open_steamcmd_path_browser)
self.button_steamcmd_browse.grid(row=11, column=2, padx=(0, 20), pady=(0, 10), sticky="ewn")
self.label_launch_args = ctk.CTkLabel(left_frame, text='Launch Parameters:')
self.label_launch_args.grid(row=12, column=1, padx=20, pady=(0, 0), columnspan=1, sticky="ws")
self.edit_startup_exe = ctk.CTkEntry(left_frame, placeholder_text="exe")
self.edit_startup_exe.grid(row=13, column=1, padx=(20,0), pady=(0, 20), columnspan=1, sticky="we")
self.edit_launch_args = ctk.CTkEntry(left_frame, placeholder_text="launch arguments")
self.edit_launch_args.grid(row=13, column=1, padx=(140,20), pady=(0, 20), columnspan=2, sticky="we")
# Check for updates button n Launch game
self.check_for_updates = ctk.CTkButton(right_frame, text="Check for updates", command=self.settings_check_for_updates)
self.check_for_updates.grid(row=1, column=1, padx=20, pady=(20, 0), sticky="n")
# self.launch_game = ctk.CTkButton(right_frame, text="Launch game", command=self.settings_launch_game)
# self.launch_game.grid(row=2, column=1, padx=20, pady=(10, 0), sticky="n")
self.reset_steamcmd = ctk.CTkButton(right_frame, text="Reset SteamCMD", command=self.settings_reset_steamcmd)
self.reset_steamcmd.grid(row=3, column=1, padx=20, pady=(10, 0), sticky="n")
self.reset_steamcmd_tooltip = CTkToolTip(self.reset_steamcmd, message="This will remove steamapps folder + all the maps that are potentioaly corrupted\nor not so use at ur own risk (could fix some issues as well)")
self.workshop_to_gamedir = ctk.CTkButton(right_frame, text="Workshop Transfer", command=self.workshop_to_gamedir_toplevel)
self.workshop_to_gamedir.grid(row=4, column=1, padx=20, pady=(10, 0), sticky="n")
self.workshop_to_gamedir_tooltip = CTkToolTip(self.workshop_to_gamedir, message="Copy/Move maps and mods from Workshop to the game directory (opens up a window)")
# appearance
self.appearance_mode_label = ctk.CTkLabel(right_frame, text="Appearance:", anchor="w")
self.appearance_mode_label.grid(row=5, column=1, padx=20, pady=(150, 0), sticky="nw")
self.appearance_mode_optionemenu = ctk.CTkOptionMenu(right_frame, values=["Light", "Dark", "System"],
command=master.change_appearance_mode_event)
self.appearance_mode_optionemenu.grid(row=6, column=1, padx=20, pady=(0, 0), sticky="nw")
self.scaling_label = ctk.CTkLabel(right_frame, text="UI Scaling:", anchor="w")
self.scaling_label.grid(row=7, column=1, padx=20, pady=(0, 0), sticky="nw")
self.scaling_optionemenu = ctk.CTkOptionMenu(right_frame, values=["60%", "70%", "80%", "90%", "100%", "110%"],
command=master.change_scaling_event)
self.scaling_optionemenu.grid(row=8, column=1, padx=20, pady=(0, 0), sticky="nw")
# self.custom_theme = ctk.CTkButton(right_frame, text="Custom theme", command=self.T7xwd_custom_theme)
# self.custom_theme.grid(row=9, column=1, padx=20, pady=(20, 0), sticky="n")
self.theme_options_label = ctk.CTkLabel(right_frame, text="Theme:", anchor="w")
self.theme_options_label.grid(row=9, column=1, padx=20, pady=(0, 0), sticky="nw")
self.theme_options = ctk.CTkOptionMenu(right_frame, values=["Default", "Blue", "Grey", "Obsidian", "Ghost","NeonBanana", "Custom"],
command=self.theme_options_func)
self.theme_options.grid(row=10, column=1, padx=20, pady=(0, 0), sticky="nw")
self.theme_options.set(value=self.load_settings("theme", "Default"))
# Reset steam on many fails
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_text = ctk.CTkLabel(left_frame, text=f"Reset steamcmd: (n of fails):", anchor="w")
self.reset_steamcmd_on_fail_text.grid(row=8, column=1, padx=20, pady=(10, 0), sticky="nw")
self.reset_steamcmd_on_fail = ctk.CTkOptionMenu(left_frame, values=["5", "10", "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=8, column=1, padx=(190, 0), pady=(10, 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_text = ctk.CTkLabel(right_frame, text=f"Download Attempts:", anchor="w")
self.reset_steamcmd_on_fail_text.grid(row=11, column=1, padx=20, pady=(0, 0), sticky="nw")
self.reset_steamcmd_on_fail = ctk.CTkOptionMenu(right_frame, values=["5", "10", "15", "20", "Custom", "Disable"], variable=self.reset_steamcmd_on_fail_var, command=self.reset_steamcmd_on_fail_func)
self.reset_steamcmd_on_fail.grid(row=12, column=1, padx=20, pady=(0, 0), sticky="nw")
self.reset_steamcmd_on_fail_tooltip = CTkToolTip(self.reset_steamcmd_on_fail, message="Number of failed download attempts before resetting SteamCMD")
self.reset_steamcmd_on_fail.set(value=self.load_settings("reset_on_fail", "10"))
# item folder naming
self.folder_options_label_var = ctk.IntVar()
self.folder_options_label_var.trace_add("write", self.enable_save_button)
self.folder_options_label = ctk.CTkLabel(left_frame, text="Items Folder Naming:", anchor="nw")
self.folder_options_label.grid(row=10, column=1, padx=20, pady=(10, 0), sticky="nw")
self.folder_options = ctk.CTkOptionMenu(left_frame, values=["PublisherID", "FolderName"], command=self.change_folder_naming,
self.folder_options_label = ctk.CTkLabel(right_frame, text="Items Folder Naming:", anchor="w")
self.folder_options_label.grid(row=13, column=1, padx=(20,0), pady=(0, 0), sticky="nw")
self.folder_options = ctk.CTkOptionMenu(right_frame, values=["PublisherID", "FolderName"], command=self.change_folder_naming,
variable=self.folder_options_label_var)
self.folder_options.grid(row=10, column=1, padx=(150, 0), pady=(3, 0), sticky="nw")
self.folder_options.grid(row=14, column=1, padx=20, pady=(0, 0), sticky="nw")
self.folder_options.set(value=self.load_settings("folder_naming", "PublisherID"))
# Check for updates button n Launch T7x
self.check_for_updates = ctk.CTkButton(right_frame, text="Check for updates", command=self.settings_check_for_updates)
self.check_for_updates.grid(row=1, column=1, padx=20, pady=(20, 0), sticky="n")
self.launch_T7x = ctk.CTkButton(right_frame, text="Launch T7x", command=self.settings_launch_T7x)
self.launch_T7x.grid(row=2, column=1, padx=20, pady=(20, 0), sticky="n")
self.reset_steamcmd = ctk.CTkButton(right_frame, text="Reset SteamCMD", command=self.settings_reset_steamcmd)
self.reset_steamcmd.grid(row=3, column=1, padx=20, pady=(20, 0), sticky="n")
self.reset_steamcmd_tooltip = CTkToolTip(self.reset_steamcmd, message="This will remove steamapps folder + all the maps that are potentioaly corrupted\nor not so use at ur own risk (could fix some issues as well)")
self.steam_to_T7x = ctk.CTkButton(right_frame, text="Steam to T7x", command=self.from_steam_to_T7x_toplevel)
self.steam_to_T7x.grid(row=5, column=1, padx=20, pady=(20, 0), sticky="n")
self.steam_to_T7x_tooltip = CTkToolTip(self.steam_to_T7x, message="Moves/copies maps and mods from steam to T7x (opens up a window)")
# appearance
self.appearance_mode_label = ctk.CTkLabel(right_frame, text="Appearance Mode:", anchor="n")
self.appearance_mode_label.grid(row=6, column=1, padx=20, pady=(20, 0))
self.appearance_mode_optionemenu = ctk.CTkOptionMenu(right_frame, values=["Light", "Dark", "System"],
command=master.change_appearance_mode_event)
self.appearance_mode_optionemenu.grid(row=7, column=1, padx=20, pady=(0, 0))
self.scaling_label = ctk.CTkLabel(right_frame, text="UI Scaling:", anchor="n")
self.scaling_label.grid(row=8, column=1, padx=20, pady=(10, 0))
self.scaling_optionemenu = ctk.CTkOptionMenu(right_frame, values=["80%", "90%", "100%", "110%", "120%"],
command=master.change_scaling_event)
self.scaling_optionemenu.grid(row=9, column=1, padx=20, pady=(0, 0))
# self.custom_theme = ctk.CTkButton(right_frame, text="Custom theme", command=self.T7xwd_custom_theme)
# self.custom_theme.grid(row=8, column=1, padx=20, pady=(20, 0), sticky="n")
self.theme_options_label = ctk.CTkLabel(right_frame, text="Themes:", anchor="n")
self.theme_options_label.grid(row=10, column=1, padx=20, pady=(10, 0))
self.theme_options = ctk.CTkOptionMenu(right_frame, values=["Default", "Blue", "Grey", "Obsidian", "Ghost","NeonBanana", "Custom"],
command=self.theme_options_func)
self.theme_options.grid(row=11, column=1, padx=20, pady=(0, 0))
self.theme_options.set(value=self.load_settings("theme", "Default"))
# Save button
self.save_button = ctk.CTkButton(self, text="Save", command=self.save_settings, state='disabled')
self.save_button.grid(row=3, column=0, padx=20, pady=(20, 20), sticky="nw")
#version
self.version_info = ctk.CTkLabel(self, text=f"{VERSION}")
self.version_info.grid(row=3, column=1, padx=20, pady=(20, 20), sticky="e")
self.version_info.grid(row=3, column=2, padx=20, pady=(20, 20), sticky="e")
def open_T7x_browser(self):
selected_folder = ctk.filedialog.askdirectory(title="Select Game Folder")
if selected_folder:
self.edit_destination_folder.delete(0, "end")
self.edit_destination_folder.insert(0, selected_folder)
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
def open_steamcmd_path_browser(self):
selected_folder = ctk.filedialog.askdirectory(title="Select SteamCMD Folder")
if selected_folder:
self.edit_steamcmd_path.delete(0, "end")
self.edit_steamcmd_path.insert(0, selected_folder)
save_config("DestinationFolder" ,self.edit_destination_folder.get())
save_config("SteamCMDPath" ,self.edit_steamcmd_path.get())
def reset_steamcmd_on_fail_func(self, option: str):
if option == "Custom":
@ -225,6 +292,9 @@ class SettingsTab(ctk.CTkFrame):
def save_settings(self):
self.save_button.configure(state='disabled')
save_config("GameExecutable", str(self.edit_startup_exe.get()) if not isNullOrWhiteSpace(self.edit_startup_exe.get()) else "BlackOps3")
save_config("LaunchParameters", str(self.edit_launch_args.get()) if not isNullOrWhiteSpace(self.edit_launch_args.get()) else " ")
if self.folder_options.get() == "PublisherID":
save_config("folder_naming", "0")
else:
@ -235,10 +305,20 @@ class SettingsTab(ctk.CTkFrame):
else:
save_config("check_items", "off")
if self.check_updates_checkbox.get():
save_config("checkforupdtes", "on")
if self.invalid_items_var.get():
save_config("update_invalid", "on")
else:
save_config("checkforupdtes", "off")
save_config("update_invalid", "off")
if self.skip_items_var.get():
save_config("skip_invalid", "on")
else:
save_config("skip_invalid", "off")
if self.check_updates_checkbox.get():
save_config("checkforupdates", "on")
else:
save_config("checkforupdates", "off")
if self.checkbox_show_console.get():
save_config("console", "on")
@ -380,8 +460,8 @@ class SettingsTab(ctk.CTkFrame):
file_to_rename = os.path.join(APPLICATION_PATH, "T7xwd_theme.json")
if os.path.exists(file_to_rename):
timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
new_name = f"T7xwd_theme_{timestamp}.json"
os.rename(file_to_rename, os.path.join(APPLICATION_PATH, new_name))
name = f"T7xwd_theme_{timestamp}.json"
os.rename(file_to_rename, os.path.join(APPLICATION_PATH, name))
if not disable_only:
show_message("Preset file renamed", "Custom preset disabled, file has been renmaed\n* Restart the app to take effect", icon="info")
@ -399,9 +479,9 @@ class SettingsTab(ctk.CTkFrame):
# make this rename to {id}_duplicate as a fallback
def rename_all_folders(self, option):
T7xFolder = main_app.app.edit_destination_folder.get()
maps_folder = os.path.join(T7xFolder, "mods")
mods_folder = os.path.join(T7xFolder, "usermaps")
gameFolder = self.edit_destination_folder.get()
mods_folder = os.path.join(gameFolder, "mods")
maps_folder = os.path.join(gameFolder, "usermaps")
folders_to_process = []
@ -412,61 +492,105 @@ class SettingsTab(ctk.CTkFrame):
folders_to_process.append(maps_folder)
if not os.path.exists(maps_folder) and not os.path.exists(mods_folder):
show_message("Warning -> Check T7x path", f"You don't have any items yet ,from now on item's folders will be named as their {option}")
show_message("Warning -> Check game path",
f"You don't have any items yet, from now on item's folders will be named as their {option}")
return 0
processed_names = set()
for folder_path in folders_to_process:
for folder_name in os.listdir(folder_path):
zone_path = os.path.join(folder_path, folder_name, "zone")
if not os.path.isdir(zone_path):
continue
if folder_name in main_app.app.library_tab.item_block_list:
continue
files = Path(folders_to_process[0]).glob("*/zone/workshop.json")
items = dict()
ignored_folders = []
json_path = os.path.join(zone_path, "workshop.json")
publisher_id = extract_json_data(json_path, 'PublisherID')
new_name = extract_json_data(json_path, option)
if folder_name == new_name:
continue
for idx, file in enumerate(files):
curr_folder_name = os.path.relpath(file, folders_to_process[0]).split("\\", 1)[0]
rename_flag = True
with open(file, 'r') as json_file:
data = json.load(json_file)
_item = {
'PublisherID': data.get('PublisherID'),
'Name': data.get(option),
'current_folder': curr_folder_name
}
if _item.get('PublisherID')!="" and int(_item.get('PublisherID'))>0:
items[idx] = _item
else:
ignored_folders.append(curr_folder_name)
if os.path.exists(json_path):
folder_to_rename = os.path.join(folder_path, folder_name)
new_folder_name = new_name
while new_folder_name in processed_names:
if new_name == publisher_id:
new_folder_name += f"_duplicated"
IDs = [x['PublisherID'] for x in items.values()]
Names = [x['Name'] for x in items.values()]
currFolder = [x['current_folder'] for x in items.values()]
def indices(lst, item):
return [i for i, x in enumerate(lst) if item in x]
def find_duplicate_items_in_list(list, items):
return dict((x, indices(list, x)) for x in [y for y in items if items.count(y) > 1])
def prep_rename(changelist, orig, new):
return changelist.append((os.path.join(folders_to_process[0], orig), os.path.join(folders_to_process[0], new)))
duplicates = find_duplicate_items_in_list(Names, Names)
duplicates_IDs = find_duplicate_items_in_list(IDs, IDs)
duplicate_idx = concatenate_sublists(duplicates.values())
changelist = []
for i in range(len(IDs)):
if i not in duplicate_idx:
prep_rename(changelist, currFolder[i], Names[i])
for v in duplicates.values():
if len(v) == 2:
if IDs[v[0]] == IDs[v[1]]:
prep_rename(changelist, currFolder[v[0]], Names[v[0]])
prep_rename(
changelist, currFolder[v[1]], Names[v[1]]+"_duplicate")
else:
prep_rename(
changelist, currFolder[v[0]], Names[v[0]]+f"_{IDs[v[0]]}")
prep_rename(
changelist, currFolder[v[1]], Names[v[1]]+f"_{IDs[v[1]]}")
if len(v) > 2:
for j, i in enumerate(v):
if i in (duplicates_IDs.get(f'{IDs[i]}') if duplicates_IDs.get(f'{IDs[i]}') is not None else []):
if i == v[0]:
if Names[i].startswith(IDs[i]):
prep_rename(
changelist, currFolder[i], Names[i])
else:
prep_rename(
changelist, currFolder[i], Names[i]+f"_{IDs[i]}")
else:
new_folder_name += f"_{publisher_id}"
if folder_name == new_folder_name:
rename_flag = False
break
new_path = os.path.join(folder_path, new_folder_name)
if Names[i].startswith(IDs[i]):
newname = Names[i]+f"_duplicate"
else:
newname = Names[i]+f"_{IDs[i]}"
while os.path.exists(new_path):
if new_name == publisher_id:
new_folder_name += f"_duplicated"
else:
new_folder_name += f"_{publisher_id}"
if folder_name == new_folder_name:
rename_flag = False
break
new_path = os.path.join(folder_path, new_folder_name)
if j > 0:
newname += '_' + str(j)
if rename_flag:
os.rename(folder_to_rename, new_path)
processed_names.add(new_folder_name)
prep_rename(changelist, currFolder[i], newname)
else:
prep_rename(
changelist, currFolder[i], Names[i]+f"_{IDs[i]}")
for n in changelist:
safe_name = nextnonexistentdir(*tuple(reversed(os.path.split(n[1]))))
if safe_name[0] in ignored_folders and safe_name[1] > 0:
os.rename(n[0], os.path.join(n[0], '{}_{}'.format(*safe_name)))
else:
os.rename(n[0], n[1])
return 1
def change_folder_naming(self, option):
main_app.app.title("T7x Workshop Downloader - Settings ➜ Loading... ⏳")
try:
if os.path.exists(main_app.app.edit_destination_folder.get()):
lib = main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True)
if os.path.exists(self.edit_destination_folder.get()):
lib = main_app.app.library_tab.load_items(self.edit_destination_folder.get(), dont_add=True)
if not "No items" in lib:
if show_message("Renaming", "Would you like to rename all your exisiting item folders now?", _return=True):
main_app.app.title("T7x Workshop Downloader - Settings ➜ Renaming... ⏳")
@ -476,13 +600,13 @@ class SettingsTab(ctk.CTkFrame):
return 0
else:
show_message("Done!", "All folders have been renamed", icon="info")
main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True)
main_app.app.library_tab.load_items(self.edit_destination_folder.get(), dont_add=True)
else:
show_message("Heads up!", "Only newly downloaded items will be affected", icon="info")
else:
show_message("Warning -> Check T7x path", f"You don't have any items yet ,from now on item's folders will be named as their {option}")
show_message("Warning -> Check game path", f"You don't have any items yet ,from now on item's folders will be named as their {option}")
else:
show_message("Warning -> Check T7x path", f"You don't have any items yet ,from now on item's folders will be named as their {option}")
show_message("Warning -> Check game path", f"You don't have any items yet ,from now on item's folders will be named as their {option}")
except Exception as e:
show_message("Error", f"Error occured \n{e}")
finally:
@ -490,7 +614,7 @@ class SettingsTab(ctk.CTkFrame):
self.save_settings()
def load_on_switch_screen(self):
self.check_updates_var.set(self.load_settings("checkforupdtes"))
self.check_updates_var.set(self.load_settings("checkforupdates"))
self.console_var.set(self.load_settings("console"))
self.reset_steamcmd_on_fail.set(value=self.load_settings("reset_on_fail", "10"))
self.estimated_progress_var.set(self.load_settings("estimated_progress", "on"))
@ -502,20 +626,20 @@ class SettingsTab(ctk.CTkFrame):
# keep last cuz of trace_add()
self.save_button.configure(state='disabled')
def settings_launch_T7x(self):
launch_T7x_func(check_config("destinationfolder"))
def settings_launch_game(self):
launch_game_func(check_config("destinationfolder"), self.edit_startup_exe.get(), self.edit_launch_args.get())
def settings_reset_steamcmd(self):
reset_steamcmd()
def from_steam_to_T7x_toplevel(self):
def workshop_to_gamedir_toplevel(self):
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)
main_app.app.library_tab.load_items(self.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 T7x")
top.title("Workshop Transfer")
_, _, x, y = get_window_size_from_registry()
top.geometry(f"+{x}+{y}")
# top.attributes('-topmost', 'true')
@ -527,9 +651,10 @@ class SettingsTab(ctk.CTkFrame):
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)
T7x_folder_label = ctk.CTkLabel(center_frame, text="T7x Folder:")
T7x_folder_entry = ctk.CTkEntry(center_frame, width=225)
game_folder_label = ctk.CTkLabel(center_frame, text="Game Folder:")
game_folder_entry = ctk.CTkEntry(center_frame, width=225)
button_T7x_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()
@ -574,11 +699,11 @@ class SettingsTab(ctk.CTkFrame):
if copy_var.get():
copy_button.configure(text=f"Start (Copy)")
def open_T7x_browser():
selected_folder = ctk.filedialog.askdirectory(title="Select T7x Folder")
def open_game_browser():
selected_folder = ctk.filedialog.askdirectory(title="Select Game Folder")
if selected_folder:
T7x_folder_entry.delete(0, "end")
T7x_folder_entry.insert(0, selected_folder)
game_folder_entry.delete(0, "end")
game_folder_entry.insert(0, selected_folder)
def open_steam_browser():
selected_folder = ctk.filedialog.askdirectory(title="Select Steam Folder (ex: C:/Program Files (x86)/Steam)")
@ -591,20 +716,20 @@ class SettingsTab(ctk.CTkFrame):
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!")
show_message("Choose operation!", "Please choose an operation, Copy or Cut files from Steam!")
return
copy_button.configure(state="disabled")
steam_folder = steam_folder_entry.get()
ws_folder = os.path.join(steam_folder, "steamapps/workshop/content/311210")
T7x_folder = T7x_folder_entry.get()
game_folder = game_folder_entry.get()
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
if not os.path.exists(T7x_folder):
show_message("Not found", "T7x folder not found, please recheck path")
if not os.path.exists(game_folder):
show_message("Not found", "game folder not found, please recheck path")
return
top.after(0, progress_text.configure(text="Loading..."))
@ -644,10 +769,10 @@ class SettingsTab(ctk.CTkFrame):
folder_name = extract_json_data(json_file_path, "publisherID")
if mod_type == "mod":
path_folder = os.path.join(T7x_folder, "mods")
path_folder = os.path.join(game_folder, "mods")
folder_name_path = os.path.join(path_folder, folder_name, "zone")
elif mod_type == "map":
path_folder = os.path.join(T7x_folder, "usermaps")
path_folder = os.path.join(game_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")
@ -669,17 +794,17 @@ class SettingsTab(ctk.CTkFrame):
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)
main_app.app.library_tab.update_item(self.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)
main_app.app.library_tab.load_items(self.edit_destination_folder.get(), dont_add=True)
return
continue
if subfolders:
main_app.app.library_tab.load_items(main_app.app.edit_destination_folder.get(), dont_add=True)
main_app.app.library_tab.load_items(self.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:
@ -699,8 +824,8 @@ class SettingsTab(ctk.CTkFrame):
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')
T7x_folder_label.grid(row=2, column=0, padx=(20, 20), pady=(10, 0), sticky='w')
T7x_folder_entry.grid(row=3, column=0, columnspan=2, padx=(0, 20), pady=(10, 10), sticky='nes')
game_folder_label.grid(row=2, column=0, padx=(20, 20), pady=(10, 0), sticky='w')
game_folder_entry.grid(row=3, column=0, columnspan=2, padx=(0, 20), pady=(10, 10), sticky='nes')
button_T7x_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')
@ -711,14 +836,14 @@ class SettingsTab(ctk.CTkFrame):
progress_color = get_button_state_colors(check_custom_theme(check_config("theme", fallback="T7xwd_theme.json")), "progress_bar_fill_color")
progress_bar.configure(progress_color=progress_color)
steam_folder_entry.insert(1, check_config("steam_folder", ""))
T7x_folder_entry.insert(1, main_app.app.edit_destination_folder.get())
button_T7x_browse.configure(command=open_T7x_browser)
game_folder_entry.insert(1, self.edit_destination_folder.get())
button_T7x_browse.configure(command=open_game_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(T7x_folder_entry)
main_app.app.create_context_menu(game_folder_entry)
copy_var.set(True)
progress_bar.set(0)
top.after(150, top.focus_force)