Skip to content

Commit a902046

Browse files
committed
refactor(core): remove extraneous methods from FolderConfig
Directories should be created/checked for existence when needed. Using `Path` objects directly is usually cleaner than using methods. feat(core): dynamically set attrs in `FolderConfig`
1 parent dfc806d commit a902046

12 files changed

Lines changed: 88 additions & 100 deletions

core/backup_manager.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22
import shutil
33

44
from core.folder_setup import folder_setup
5+
from core.util.file import delete
56

67
log = logging.getLogger()
78

89

910
def prepare_working_copy() -> bool:
1011
try:
11-
folder_setup.cleanup_temp_folders()
12-
folder_setup.create_required_folders()
12+
delete(folder_setup.temp_dir, not_exist_ok=True)
1313

1414
backup_particles_dir = folder_setup.backup_dir / "particles"
1515
particle_dest_dir = folder_setup.temp_to_be_referenced_dir
16+
backup_particles_dir.mkdir(parents=True, exist_ok=True)
17+
particle_dest_dir.mkdir(parents=True, exist_ok=True)
1618

1719
for pcf_file in backup_particles_dir.glob("*.pcf"):
1820
shutil.copy2(pcf_file, particle_dest_dir / pcf_file.name)

core/folder_setup.py

Lines changed: 36 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,22 @@
1-
import os
2-
import shutil
3-
from dataclasses import dataclass, field
1+
from dataclasses import dataclass
42
from pathlib import Path
5-
from typing import Optional
6-
7-
from valve_parsers import PCFFile
83

94
# INFO: This file just allows package maintainers to set whether this application should act as if it is a portable installation.
105
# They can easily modify this file and set these values, e.g.
116
# `printf '%s\n' 'portable = False' >core/are_we_portable.py`
127
# This will make the application use paths outside the installation location.
138
from core.are_we_portable import portable
149
from core.constants import PROGRAM_AUTHOR, PROGRAM_NAME
15-
from core.handlers.pcf_handler import get_parent_elements
1610

1711

1812
@dataclass
1913
class FolderConfig:
2014
# configuration class for managing folder paths
21-
install_dir = Path(os.path.dirname(os.path.abspath(__file__))).parent # INFO: I'm not too sure if this can break or not, oh well
15+
install_dir = Path(__file__).resolve().parent.parent
16+
data_dir = install_dir / 'data'
17+
2218
portable = portable # make sure it is accessible via self.portable
2319

24-
# TODO: allow windows users to use non-portable installs (would allow us to remove this entire platform check)
2520
if portable:
2621
# default portable values
2722
project_dir = install_dir
@@ -33,81 +28,41 @@ class FolderConfig:
3328
project_dir = Path(platformdirs.user_data_dir(PROGRAM_NAME, PROGRAM_AUTHOR))
3429
settings_dir = Path(platformdirs.user_config_dir(PROGRAM_NAME, PROGRAM_AUTHOR))
3530

36-
base_default_pcf: Optional[PCFFile] = field(default=None)
37-
base_default_parents: Optional[set[str]] = field(default=None)
38-
39-
# main folder names
40-
_backup_folder = "backup"
41-
_mods_folder = "mods"
42-
43-
# mods subdir
44-
_mods_particles_folder = "particles"
45-
_mods_addons_folder = "addons"
46-
47-
# temp and it's nested folders (to be cleared every run)
48-
_temp_folder = "temp"
49-
_temp_to_be_processed_folder = "to_be_processed"
50-
_temp_to_be_referenced_folder = "to_be_referenced"
51-
_temp_to_be_patched_folder = "to_be_patched"
52-
_temp_to_be_vpk_folder = "to_be_vpk"
31+
__deps = {
32+
'project_dir': {
33+
'backup_dir' : lambda self: self.project_dir / 'backup',
34+
'mods_dir' : lambda self: self.project_dir / 'mods',
35+
'temp_dir' : lambda self: self.project_dir / 'temp',
36+
},
37+
'mods_dir' : {
38+
'particles_dir' : lambda self: self.mods_dir / 'particles',
39+
'addons_dir' : lambda self: self.mods_dir / 'addons',
40+
},
41+
'temp_dir' : {
42+
'temp_download_dir' : lambda self: self.temp_dir / 'download',
43+
'temp_to_be_processed_dir' : lambda self: self.temp_dir / 'to_be_processed',
44+
'temp_to_be_referenced_dir' : lambda self: self.temp_dir / 'to_be_referenced',
45+
'temp_to_be_patched_dir' : lambda self: self.temp_dir / 'to_be_patched',
46+
'temp_to_be_vpk_dir' : lambda self: self.temp_dir / 'to_be_vpk',
47+
},
48+
}
5349

5450
def __post_init__(self):
55-
self.backup_dir = self.project_dir / self._backup_folder
56-
self.data_dir = self.install_dir / "data"
57-
58-
self.mods_dir = self.project_dir / self._mods_folder
59-
self.particles_dir = self.mods_dir / self._mods_particles_folder
60-
self.addons_dir = self.mods_dir / self._mods_addons_folder
61-
62-
self.temp_dir = self.project_dir / self._temp_folder
63-
self.temp_download_dir = self.temp_dir / 'download'
64-
self.temp_to_be_processed_dir = self.temp_dir / self._temp_to_be_processed_folder
65-
self.temp_to_be_referenced_dir = self.temp_dir / self._temp_to_be_referenced_folder
66-
self.temp_to_be_patched_dir = self.temp_dir / self._temp_to_be_patched_folder
67-
self.temp_to_be_vpk_dir = self.temp_dir / self._temp_to_be_vpk_folder
68-
69-
def create_required_folders(self) -> None:
70-
folders = [
71-
self.mods_dir,
72-
self.addons_dir,
73-
self.particles_dir,
74-
75-
self.temp_dir,
76-
self.temp_to_be_processed_dir,
77-
self.temp_to_be_referenced_dir,
78-
self.temp_to_be_patched_dir,
79-
self.temp_to_be_vpk_dir
80-
]
81-
82-
for folder in folders:
83-
folder.mkdir(parents=True, exist_ok=True)
84-
85-
def initialize_pcf(self):
86-
if self.temp_to_be_referenced_dir.exists():
87-
default_base_path = self.temp_to_be_referenced_dir / "disguise.pcf"
88-
if default_base_path.exists():
89-
self.base_default_pcf = PCFFile(default_base_path).decode()
90-
self.base_default_parents = get_parent_elements(self.base_default_pcf)
91-
92-
def cleanup_temp_folders(self) -> None:
93-
# anything put in temp/ will be gone !!!!!
94-
if self.temp_dir.exists():
95-
shutil.rmtree(self.temp_dir)
96-
self.base_default_pcf = None
97-
self.base_default_parents = None
98-
99-
def get_temp_path(self, filename: str) -> Path:
100-
return self.temp_dir / filename
101-
102-
def get_output_path(self, filename: str) -> Path:
103-
return self.temp_to_be_processed_dir / filename
51+
for dep, props in self.__deps.items():
52+
for attr, setter in props.items():
53+
super().__setattr__(attr, setter(self))
10454

105-
def get_backup_path(self, filename: str) -> Path:
106-
return self.backup_dir / filename
55+
def __setattr__(self, attr, value):
56+
_super = super()
57+
_super.__setattr__(attr, value)
10758

108-
def get_game_files_path(self, filename: str) -> Path:
109-
return self.temp_to_be_referenced_dir / filename
59+
if attr in self.__deps:
60+
for _attr, setter in self.__deps[attr].items():
61+
_super.__setattr__(_attr, setter(self))
62+
else:
63+
for dep, props in tuple(self.__deps.items()):
64+
if attr in props:
65+
del props[attr]
11066

11167

112-
# create a default instance for import
113-
folder_setup = FolderConfig()
68+
folder_setup = FolderConfig() # create a default instance for import

core/handlers/file_handler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ def process_file(self, file_name: str, content) -> bool | None:
6969
full_path = file_name
7070

7171
# create temp file for processing in working directory
72-
temp_path = folder_setup.get_temp_path(f"temp_{Path(file_name).name}")
72+
temp_path = folder_setup.temp_dir / f"temp_{Path(file_name).name}"
73+
temp_path.parent.mkdir(parents=True, exist_ok=True)
7374

7475
try:
7576
# get original file info

core/operations/advanced_particle_merger.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,11 @@ def preprocess_vpk(self, vpk_path: Path) -> None:
9292
for particle in particles_filter:
9393
for particle_file_target, elements_to_extract, source_pcf in (
9494
rebuild_particle_files(particle, self.particle_map)):
95-
output_path = folder_setup.get_output_path(
96-
f"{len(self.vpk_groups[vpk_folder_name][particle_file_target])}_{particle_file_target}")
95+
output_path = (
96+
folder_setup.temp_to_be_processed_dir
97+
/ f"{len(self.vpk_groups[vpk_folder_name][particle_file_target])}_{particle_file_target}"
98+
)
99+
output_path.parent.mkdir(parents=True, exist_ok=True)
97100
extract_elements(source_pcf, elements_to_extract).encode(output_path)
98101
self.vpk_groups[vpk_folder_name][particle_file_target].append(output_path)
99102

@@ -121,6 +124,7 @@ def process_vpk_group(self, vpk_name: str, out_dir: Path) -> None:
121124

122125
if elements_we_still_need:
123126
game_file_path = folder_setup.temp_to_be_referenced_dir / particle_group
127+
game_file_path.parent.mkdir(parents=True, exist_ok=True) # INFO: technically not necessary, but PCFFile does not check if `input_file` exists, let alone its parent dir
124128
game_file_in = PCFFile(game_file_path).decode()
125129
game_elements = extract_elements(game_file_in, elements_we_still_need)
126130

@@ -143,5 +147,5 @@ def process_vpk_group(self, vpk_name: str, out_dir: Path) -> None:
143147
actual_particles.parent.mkdir(parents=True, exist_ok=True)
144148
result.encode(actual_particles)
145149

146-
for file in folder_setup.temp_to_be_processed_dir.iterdir():
150+
for file in folder_setup.temp_to_be_processed_dir.glob('*'):
147151
file.unlink()

core/operations/file_processors.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from valve_parsers import PCFFile, VPKFile
55

6+
from core.handlers.pcf_handler import get_parent_elements
7+
68
log = logging.getLogger()
79

810

@@ -80,6 +82,16 @@ def should_process_file(file_path: str) -> bool:
8082
return any(path in path_lower for path in target_paths) and path_lower.endswith('.vmt')
8183

8284

85+
def initialize_pcf(dir: Path) -> tuple[PCFFile, set[str]]:
86+
default_base_path = dir / 'disguise.pcf'
87+
88+
if not default_base_path.exists():
89+
raise FileNotFoundError(f"[Errno 2] No such file or directory: {default_base_path}")
90+
91+
default_base_pcf = PCFFile(default_base_path).decode()
92+
return default_base_pcf, get_parent_elements(default_base_pcf)
93+
94+
8395
get_val = [
8496
[34, 36, 105, 103, 110, 111, 114, 101, 122, 34, 9, 34, 49, 34],
8597
[34, 36, 105, 103, 110, 111, 114, 101, 122, 34, 9, 49],

core/particle_splits.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ def migrate_old_particle_files():
1414
mods_to_migrate = []
1515
mods_missing_source = []
1616

17+
folder_setup.particles_dir.mkdir(parents=True, exist_ok=True)
18+
1719
# check each mod for old format files
1820
for mod_dir in folder_setup.particles_dir.iterdir():
1921
if not mod_dir.is_dir():

core/quickprecache/quick_precache.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def make_precache_sub_list_file(self, filename: str, data: str) -> bool:
157157
# create a QC file and compile it with StudioMDL
158158
try:
159159
# create a temporary file
160+
folder_setup.temp_dir.mkdir(parents=True, exist_ok=True)
160161
temp_file = tempfile.NamedTemporaryFile(
161162
mode='w',
162163
suffix='.qc',
@@ -189,6 +190,7 @@ def make_precache_list_file(self) -> bool:
189190
# create the main precache.qc file that includes all subfiles
190191
try:
191192
# create the main QC file
193+
folder_setup.temp_dir.mkdir(parents=True, exist_ok=True)
192194
temp_file = tempfile.NamedTemporaryFile(
193195
mode='w',
194196
suffix='.qc',

gui/addon_manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def __init__(self, settings_manager):
1919

2020
def load_addons(self, addons_list):
2121
addons_dir = folder_setup.addons_dir
22+
addons_dir.mkdir(parents=True, exist_ok=True)
2223
# block the signal so the addons list doesn't clear in the app_settings.json
2324
addons_list.blockSignals(True)
2425
addons_list.clear()
@@ -95,6 +96,7 @@ def load_addon_info(addon_name: str) -> dict:
9596
def scan_addon_contents(self):
9697
addon_metadata = self.settings_manager.get_addon_metadata() or {}
9798
addons_dir = folder_setup.addons_dir
99+
addons_dir.mkdir(parents=True, exist_ok=True)
98100
addons = [d for d in addons_dir.iterdir() if d.is_dir()]
99101
processed = 0
100102
new_or_updated = 0

gui/drag_and_drop.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def get_mod_particle_files():
128128
all_particles = set()
129129

130130
# scan directories
131+
folder_setup.particles_dir.mkdir(parents=True, exist_ok=True)
131132
for vpk_dir in folder_setup.particles_dir.iterdir():
132133
if vpk_dir.is_dir():
133134
particle_dir = vpk_dir / "actual_particles"
@@ -210,6 +211,7 @@ def apply_particle_selections(self):
210211
source_file = source_particles_dir / (particle_file + ".pcf")
211212
if source_file.exists():
212213
# copy particle file to to_be_patched
214+
folder_setup.temp_to_be_patched_dir.mkdir(parents=True, exist_ok=True)
213215
shutil.copy2(source_file, folder_setup.temp_to_be_patched_dir / (particle_file + ".pcf"))
214216
# get particle file mats from attrib
215217
pcf = PCFFile(source_file).decode()
@@ -267,6 +269,7 @@ def apply_particle_selections(self):
267269
merged = pcf_parts[0]
268270

269271
output_path = folder_setup.temp_to_be_patched_dir / original_file
272+
output_path.parent.mkdir(parents=True, exist_ok=True)
270273
merged.encode(output_path)
271274

272275
for split_file in split_files_in_temp:
@@ -586,7 +589,6 @@ def dropEvent(self, event):
586589

587590
self.setProperty('dragOver', False)
588591
self.style().polish(self)
589-
folder_setup.create_required_folders()
590592

591593
# collect all dropped items
592594
dropped_items = []

gui/interface.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@
1717
)
1818
from core.folder_setup import folder_setup
1919
from core.handlers.file_handler import FileHandler, copy_config_files, generate_config
20+
from core.handlers.paint_handler import disable_paints, enable_paints
2021
from core.handlers.pcf_handler import (
2122
check_parents,
2223
restore_particle_files,
2324
update_materials,
2425
)
25-
from core.handlers.paint_handler import disable_paints, enable_paints
2626
from core.handlers.skybox_handler import handle_skybox_mods, restore_skybox_files
2727
from core.handlers.sound_handler import SoundHandler
28-
from core.operations.file_processors import game_type, get_from_custom_dir
28+
from core.operations.file_processors import (
29+
game_type,
30+
get_from_custom_dir,
31+
initialize_pcf,
32+
)
2933
from core.operations.for_the_love_of_god_add_vmts_to_your_mods import (
3034
generate_missing_vmt_files,
3135
)
@@ -79,7 +83,7 @@ def install(self, tf_path: str, selected_addons: List[str], mod_drop_zone=None):
7983
try:
8084
working_vpk_path = Path(tf_path) / get_vpk_name(tf_path)
8185
file_handler = FileHandler(str(working_vpk_path))
82-
folder_setup.initialize_pcf()
86+
base_default_pcf, base_default_parents = initialize_pcf(folder_setup.temp_to_be_referenced_dir)
8387
self.update_progress(0, "Installing addons...")
8488

8589
total_files = 0
@@ -236,6 +240,7 @@ def install(self, tf_path: str, selected_addons: List[str], mod_drop_zone=None):
236240
if not target_path.exists():
237241
# copy from game_files if not in
238242
source_path = folder_setup.temp_to_be_referenced_dir / duplicate_effect
243+
target_path.parent.mkdir(parents=True, exist_ok=True)
239244
if source_path.exists():
240245
extract_elements(PCFFile(source_path).decode(),
241246
load_particle_system_map(folder_setup.data_dir / 'particle_system_map.json')
@@ -262,12 +267,11 @@ def install(self, tf_path: str, selected_addons: List[str], mod_drop_zone=None):
262267
base_name = pcf_file.name
263268

264269
mod_pcf = PCFFile(pcf_file).decode()
265-
266-
if base_name != folder_setup.base_default_pcf.input_file.name and check_parents(mod_pcf, folder_setup.base_default_parents):
270+
if base_name != base_default_pcf.input_file.name and check_parents(mod_pcf, base_default_parents):
267271
continue
268272

269-
if base_name == folder_setup.base_default_pcf.input_file.name:
270-
mod_pcf = update_materials(folder_setup.base_default_pcf, mod_pcf)
273+
if base_name == base_default_pcf.input_file.name:
274+
mod_pcf = update_materials(base_default_pcf, mod_pcf)
271275

272276
# process the mod PCF
273277
processed_pcf = remove_duplicate_elements(mod_pcf)
@@ -354,6 +358,7 @@ def install(self, tf_path: str, selected_addons: List[str], mod_drop_zone=None):
354358
split_size = 2 ** 31
355359
vpk_base_path = custom_dir / CUSTOM_VPK_NAME.replace('.vpk', '')
356360

361+
custom_content_dir.mkdir(parents=True, exist_ok=True) # INFO: technically not necessary, but VPKFile does not check if `source_dir` exists
357362
if not VPKFile.create(str(custom_content_dir), str(vpk_base_path), split_size):
358363
raise Exception("Failed to create custom VPK")
359364

0 commit comments

Comments
 (0)