Coverage for enderchest/filesystem.py: 95%

37 statements  

« 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""" 

2 

3import itertools 

4import os 

5from collections.abc import Iterable 

6from pathlib import Path 

7 

8from .loggers import INVENTORY_LOGGER 

9 

10ENDER_CHEST_FOLDER_NAME = "EnderChest" 

11 

12ENDER_CHEST_CONFIG_NAME = "enderchest.cfg" 

13 

14SHULKER_BOX_CONFIG_NAME = "shulkerbox.cfg" 

15 

16PLACE_CACHE_NAME = ".place_cache.json" 

17 

18 

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 

22 

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`. 

33 

34 Returns 

35 ------- 

36 Path 

37 The path to the EnderChest folder 

38 

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 

46 

47 

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 

51 

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`. 

61 

62 Returns 

63 ------- 

64 Path 

65 The path to the EnderChest config file 

66 

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) 

72 

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 

78 

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 

84 

85 

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 

89 

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 

97 

98 Returns 

99 ------- 

100 Path 

101 The path to the shulker box folder 

102 

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 

108 

109 

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 

113 

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 

121 

122 Returns 

123 ------- 

124 Path 

125 The path to the shulker box folder 

126 

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 

132 

133 

134def place_cache(minecraft_root: Path) -> Path: 

135 """Given a minecraft root directory, return the path to the EnderChest 

136 place cache file 

137 

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) 

143 

144 Returns 

145 ------- 

146 Path 

147 The path to the place cache file 

148 

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 

154 

155 

156def shulker_box_configs(minecraft_root: Path) -> Iterable[Path]: 

157 """Find all shulker box configs on the system 

158 

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) 

164 

165 Returns 

166 ------- 

167 list-like of paths 

168 The paths to all the shulker box configs on the system 

169 

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}") 

177 

178 

179def minecraft_folders(search_path: Path) -> Iterable[Path]: 

180 """Find all .minecraft folders within a given search path 

181 

182 Parameters 

183 ---------- 

184 search_path : Path 

185 The directory to search 

186 

187 Returns 

188 ------- 

189 list-like of paths 

190 The paths to all the .minecraft folders this method could find 

191 

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 ) 

200 

201 

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. 

207 

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`. 

220 

221 Returns 

222 ------- 

223 bool 

224 True if the path is inside of the EnderChest folder. False otherwise. 

225 

226 Notes 

227 ----- 

228 This method only checks the *direct target* of the link as opposed to the 

229 fully resolved path. 

230 

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 ) 

244 

245 target = os.readlink(link) 

246 if not os.path.isabs(target): 

247 target = os.path.normpath(link.parent / target) 

248 

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 

257 

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