Coverage for enderchest/filesystem.py: 94%

36 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-05-04 01:41 +0000

1"""Functionality for managing the EnderChest and shulker box config files and folders""" 

2 

3import os 

4from pathlib import Path 

5from typing import Iterable 

6 

7from .loggers import INVENTORY_LOGGER 

8 

9ENDER_CHEST_FOLDER_NAME = "EnderChest" 

10 

11ENDER_CHEST_CONFIG_NAME = "enderchest.cfg" 

12 

13SHULKER_BOX_CONFIG_NAME = "shulkerbox.cfg" 

14 

15PLACE_CACHE_NAME = ".place_cache.json" 

16 

17 

18def ender_chest_folder(minecraft_root: Path, check_exists: bool = True) -> Path: 

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

20 folder 

21 

22 Parameters 

23 ---------- 

24 minecraft_root : Path 

25 The root directory that your minecraft stuff (or, at least, the one 

26 that's the parent of your EnderChest folder) 

27 check_exists : bool, optional 

28 By default, this method will raise an error if no EnderChest exists 

29 at that location (meaning no folder or no enderchest config file in 

30 that folder). To disable that check, call this method with 

31 `check_exists=False`. 

32 

33 Returns 

34 ------- 

35 Path 

36 The path to the EnderChest folder 

37 

38 Raises 

39 ------ 

40 FileNotFoundError 

41 If no valid EnderChest installation exists within the given 

42 minecraft root (and checking hasn't been disabled) 

43 """ 

44 return ender_chest_config(minecraft_root, check_exists=check_exists).parent 

45 

46 

47def ender_chest_config(minecraft_root, check_exists: bool = True) -> Path: 

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

49 config file 

50 

51 Parameters 

52 ---------- 

53 minecraft_root : Path 

54 The root directory that your minecraft stuff (or, at least, the one 

55 that's the parent of your EnderChest folder) 

56 check_exists : bool, optional 

57 By default, this method will raise an error if the enderchest config 

58 file does not already exist. To disable that check, call this method 

59 with `check_exists=False`. 

60 

61 Returns 

62 ------- 

63 Path 

64 The path to the EnderChest config file 

65 

66 Raises 

67 ------ 

68 FileNotFoundError 

69 If the EnderChest config file isn't where it's supposed to be (and 

70 checking hasn't been disabled) 

71 

72 Notes 

73 ----- 

74 This method does not check if the config file is valid 

75 """ 

76 config_path = minecraft_root / ENDER_CHEST_FOLDER_NAME / ENDER_CHEST_CONFIG_NAME 

77 

78 if check_exists and not config_path.exists(): 

79 raise FileNotFoundError( 

80 f"No valid EnderChest installation exists within {minecraft_root}" 

81 ) 

82 return config_path 

83 

84 

85def shulker_box_root(minecraft_root: Path, shulker_box_name: str) -> Path: 

86 """Generate the path to the root of a shulker box, given its name and the 

87 minecraft root directory 

88 

89 Parameters 

90 ---------- 

91 minecraft_root : Path 

92 The root directory that your minecraft stuff (or, at least, the one 

93 that's the parent of your EnderChest folder) 

94 shulker_box_name : str 

95 The name of the shulker box to resolve 

96 

97 Returns 

98 ------- 

99 Path 

100 The path to the shulker box folder 

101 

102 Notes 

103 ----- 

104 This method does not check if a shulker box exists at that location 

105 """ 

106 return ender_chest_folder(minecraft_root) / shulker_box_name 

107 

108 

109def shulker_box_config(minecraft_root: Path, shulker_box_name: str) -> Path: 

110 """Generate the path to a shulker box config file, given its name and 

111 the minecraft root directory 

112 

113 Parameters 

114 ---------- 

115 minecraft_root : Path 

116 The root directory that your minecraft stuff (or, at least, the one 

117 that's the parent of your EnderChest folder) 

118 shulker_box_name : str 

119 The name of the shulker box to resolve 

120 

121 Returns 

122 ------- 

123 Path 

124 The path to the shulker box folder 

125 

126 Notes 

127 ----- 

128 This method does not check if a shulker box config exists at that location 

129 """ 

130 return shulker_box_root(minecraft_root, shulker_box_name) / SHULKER_BOX_CONFIG_NAME 

131 

132 

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

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

135 place cache file 

136 

137 Parameters 

138 ---------- 

139 minecraft_root : Path 

140 The root directory that your minecraft stuff (or, at least, the one 

141 that's the parent of your EnderChest folder) 

142 

143 Returns 

144 ------- 

145 Path 

146 The path to the place cache file 

147 

148 Notes 

149 ----- 

150 This method does not check if the cache file is valid or if it even exists 

151 """ 

152 return ender_chest_folder(minecraft_root) / PLACE_CACHE_NAME 

153 

154 

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

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

157 

158 Parameters 

159 ---------- 

160 minecraft_root : Path 

161 The root directory that your minecraft stuff (or, at least, the one 

162 that's the parent of your EnderChest folder) 

163 

164 Returns 

165 ------- 

166 list-like of paths 

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

168 

169 Notes 

170 ----- 

171 This method does not check to make sure those config files are valid, 

172 just that they exist 

173 """ 

174 INVENTORY_LOGGER.debug(f"Searching for shulker configs within {minecraft_root}") 

175 return ender_chest_folder(minecraft_root).glob(f"*/{SHULKER_BOX_CONFIG_NAME}") 

176 

177 

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

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

180 

181 Parameters 

182 ---------- 

183 search_path : Path 

184 The directory to search 

185 

186 Returns 

187 ------- 

188 list-like of paths 

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

190 

191 Notes 

192 ----- 

193 This method does not check to make sure that those .minecraft folders 

194 contain valid minecraft instances, just that they exist 

195 """ 

196 return search_path.rglob(".minecraft") 

197 

198 

199def links_into_enderchest( 

200 minecraft_root: Path, link: Path, check_exists: bool = True 

201) -> bool: 

202 """Determine whether a symlink's target is inside the EnderChest specified 

203 by the Minecraft root. 

204 

205 Parameters 

206 ---------- 

207 minecraft_root : Path 

208 The root directory that your minecraft stuff (or, at least, the one 

209 that's the parent of your EnderChest folder) 

210 link : Path 

211 The link to check 

212 check_exists : bool, optional 

213 By default, this method will raise an error if no EnderChest exists 

214 at that location (meaning no folder or no enderchest config file in 

215 that folder). To disable that check, call this method with 

216 `check_exists=False`. 

217 

218 Returns 

219 ------- 

220 bool 

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

222 

223 Notes 

224 ----- 

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

226 fully resolved path. 

227 

228 Raises 

229 ------ 

230 FileNotFoundError 

231 If no valid EnderChest installation exists within the given 

232 minecraft root (and checking hasn't been disabled) 

233 OSError 

234 If the link provided isn't actually a symbolic link 

235 """ 

236 chest_folder = os.path.normpath( 

237 ender_chest_folder(minecraft_root, check_exists=check_exists) 

238 .expanduser() 

239 .absolute() 

240 ) 

241 

242 target = os.readlink(link) 

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

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

245 

246 # Windows shenanigans: https://bugs.python.org/issue42957 

247 if target.startswith(("\\\\?\\", "\\??\\")): # pragma: no cover 

248 try: 

249 os.stat(target[4:]) 

250 target = target[4:] 

251 except (OSError, FileNotFoundError): 

252 # then maybe this is somehow legit 

253 pass 

254 

255 # there's probably a better way to check if a file is inside a sub-path 

256 try: 

257 common_root = os.path.commonpath([target, chest_folder]) 

258 except ValueError: # if they have no common root 

259 common_root = "" 

260 return common_root == chest_folder