Coverage for enderchest/filesystem.py: 95%
37 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-28 20:32 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-03-28 20:32 +0000
1"""Functionality for managing the EnderChest and shulker box config files and folders"""
3import itertools
4import os
5from collections.abc import Iterable
6from pathlib import Path
8from .loggers import INVENTORY_LOGGER
10ENDER_CHEST_FOLDER_NAME = "EnderChest"
12ENDER_CHEST_CONFIG_NAME = "enderchest.cfg"
14SHULKER_BOX_CONFIG_NAME = "shulkerbox.cfg"
16PLACE_CACHE_NAME = ".place_cache.json"
19def ender_chest_folder(minecraft_root: Path, check_exists: bool = True) -> Path:
20 """Given a minecraft root directory, return the path to the EnderChest
21 folder
23 Parameters
24 ----------
25 minecraft_root : Path
26 The root directory that your minecraft stuff (or, at least, the one
27 that's the parent of your EnderChest folder)
28 check_exists : bool, optional
29 By default, this method will raise an error if no EnderChest exists
30 at that location (meaning no folder or no enderchest config file in
31 that folder). To disable that check, call this method with
32 `check_exists=False`.
34 Returns
35 -------
36 Path
37 The path to the EnderChest folder
39 Raises
40 ------
41 FileNotFoundError
42 If no valid EnderChest installation exists within the given
43 minecraft root (and checking hasn't been disabled)
44 """
45 return ender_chest_config(minecraft_root, check_exists=check_exists).parent
48def ender_chest_config(minecraft_root, check_exists: bool = True) -> Path:
49 """Given a minecraft root directory, return the path to the EnderChest
50 config file
52 Parameters
53 ----------
54 minecraft_root : Path
55 The root directory that your minecraft stuff (or, at least, the one
56 that's the parent of your EnderChest folder)
57 check_exists : bool, optional
58 By default, this method will raise an error if the enderchest config
59 file does not already exist. To disable that check, call this method
60 with `check_exists=False`.
62 Returns
63 -------
64 Path
65 The path to the EnderChest config file
67 Raises
68 ------
69 FileNotFoundError
70 If the EnderChest config file isn't where it's supposed to be (and
71 checking hasn't been disabled)
73 Notes
74 -----
75 This method does not check if the config file is valid
76 """
77 config_path = minecraft_root / ENDER_CHEST_FOLDER_NAME / ENDER_CHEST_CONFIG_NAME
79 if check_exists and not config_path.exists():
80 raise FileNotFoundError(
81 f"No valid EnderChest installation exists within {minecraft_root}"
82 )
83 return config_path
86def shulker_box_root(minecraft_root: Path, shulker_box_name: str) -> Path:
87 """Generate the path to the root of a shulker box, given its name and the
88 minecraft root directory
90 Parameters
91 ----------
92 minecraft_root : Path
93 The root directory that your minecraft stuff (or, at least, the one
94 that's the parent of your EnderChest folder)
95 shulker_box_name : str
96 The name of the shulker box to resolve
98 Returns
99 -------
100 Path
101 The path to the shulker box folder
103 Notes
104 -----
105 This method does not check if a shulker box exists at that location
106 """
107 return ender_chest_folder(minecraft_root) / shulker_box_name
110def shulker_box_config(minecraft_root: Path, shulker_box_name: str) -> Path:
111 """Generate the path to a shulker box config file, given its name and
112 the minecraft root directory
114 Parameters
115 ----------
116 minecraft_root : Path
117 The root directory that your minecraft stuff (or, at least, the one
118 that's the parent of your EnderChest folder)
119 shulker_box_name : str
120 The name of the shulker box to resolve
122 Returns
123 -------
124 Path
125 The path to the shulker box folder
127 Notes
128 -----
129 This method does not check if a shulker box config exists at that location
130 """
131 return shulker_box_root(minecraft_root, shulker_box_name) / SHULKER_BOX_CONFIG_NAME
134def place_cache(minecraft_root: Path) -> Path:
135 """Given a minecraft root directory, return the path to the EnderChest
136 place cache file
138 Parameters
139 ----------
140 minecraft_root : Path
141 The root directory that your minecraft stuff (or, at least, the one
142 that's the parent of your EnderChest folder)
144 Returns
145 -------
146 Path
147 The path to the place cache file
149 Notes
150 -----
151 This method does not check if the cache file is valid or if it even exists
152 """
153 return ender_chest_folder(minecraft_root) / PLACE_CACHE_NAME
156def shulker_box_configs(minecraft_root: Path) -> Iterable[Path]:
157 """Find all shulker box configs on the system
159 Parameters
160 ----------
161 minecraft_root : Path
162 The root directory that your minecraft stuff (or, at least, the one
163 that's the parent of your EnderChest folder)
165 Returns
166 -------
167 list-like of paths
168 The paths to all the shulker box configs on the system
170 Notes
171 -----
172 This method does not check to make sure those config files are valid,
173 just that they exist
174 """
175 INVENTORY_LOGGER.debug(f"Searching for shulker configs within {minecraft_root}")
176 return ender_chest_folder(minecraft_root).glob(f"*/{SHULKER_BOX_CONFIG_NAME}")
179def minecraft_folders(search_path: Path) -> Iterable[Path]:
180 """Find all .minecraft folders within a given search path
182 Parameters
183 ----------
184 search_path : Path
185 The directory to search
187 Returns
188 -------
189 list-like of paths
190 The paths to all the .minecraft folders this method could find
192 Notes
193 -----
194 This method does not check to make sure that those .minecraft folders
195 contain valid minecraft instances, just that they exist
196 """
197 return itertools.chain(
198 search_path.rglob(".minecraft"), search_path.rglob("minecraft")
199 )
202def links_into_enderchest(
203 minecraft_root: Path, link: Path, check_exists: bool = True
204) -> bool:
205 """Determine whether a symlink's target is inside the EnderChest specified
206 by the Minecraft root.
208 Parameters
209 ----------
210 minecraft_root : Path
211 The root directory that your minecraft stuff (or, at least, the one
212 that's the parent of your EnderChest folder)
213 link : Path
214 The link to check
215 check_exists : bool, optional
216 By default, this method will raise an error if no EnderChest exists
217 at that location (meaning no folder or no enderchest config file in
218 that folder). To disable that check, call this method with
219 `check_exists=False`.
221 Returns
222 -------
223 bool
224 True if the path is inside of the EnderChest folder. False otherwise.
226 Notes
227 -----
228 This method only checks the *direct target* of the link as opposed to the
229 fully resolved path.
231 Raises
232 ------
233 FileNotFoundError
234 If no valid EnderChest installation exists within the given
235 minecraft root (and checking hasn't been disabled)
236 OSError
237 If the link provided isn't actually a symbolic link
238 """
239 chest_folder = os.path.normpath(
240 ender_chest_folder(minecraft_root, check_exists=check_exists)
241 .expanduser()
242 .absolute()
243 )
245 target = os.readlink(link)
246 if not os.path.isabs(target):
247 target = os.path.normpath(link.parent / target)
249 # Windows shenanigans: https://bugs.python.org/issue42957
250 if target.startswith(("\\\\?\\", "\\??\\")): # pragma: no cover
251 try:
252 os.stat(target[4:])
253 target = target[4:]
254 except (OSError, FileNotFoundError):
255 # then maybe this is somehow legit
256 pass
258 # there's probably a better way to check if a file is inside a sub-path
259 try:
260 common_root = os.path.commonpath([target, chest_folder])
261 except ValueError: # if they have no common root
262 common_root = ""
263 return common_root == chest_folder