Coverage for enderchest/uninstall.py: 95%
66 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-04 01:41 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-04 01:41 +0000
1"""Functionality for copying all files into their instances"""
3import logging
4import os
5import shutil
6from pathlib import Path
7from typing import Iterable
9from . import filesystem as fs
10from .enderchest import create_ender_chest
11from .instance import InstanceSpec
12from .inventory import load_ender_chest, load_ender_chest_instances
13from .loggers import BREAK_LOGGER, IMPORTANT
14from .prompt import confirm
17def break_ender_chest(minecraft_root: Path) -> None:
18 """Replace all instance symlinks with their actual targets, effectively
19 "uninstalling" EnderChest
21 Parameters
22 ----------
23 minecraft_root : Path
24 The root directory that your minecraft stuff (or, at least, the one
25 that's the parent of your EnderChest folder)
26 """
27 instances = load_ender_chest_instances(minecraft_root, log_level=IMPORTANT)
28 if not instances:
29 BREAK_LOGGER.error("Aborting.")
30 return
32 BREAK_LOGGER.warning(
33 "Are you sure you want to uninstall this EnderChest?"
34 "\nDoing so will replace ALL the symlinks in each of the above instances"
35 "\nwith copies of their EnderChest-linked targets."
36 "\n\nTHIS CANNOT EASILY BE UNDONE!!"
37 )
38 if not confirm(default=False):
39 BREAK_LOGGER.error("Aborting.")
40 return
42 chest_folder = fs.ender_chest_folder(minecraft_root)
43 _break(chest_folder, instances)
45 BREAK_LOGGER.log(
46 IMPORTANT,
47 "EnderChest has been uninstalled."
48 "\nYou may now delete %s"
49 "\nand uninstall the EnderChest package",
50 chest_folder,
51 )
54def break_instances(minecraft_root: Path, instance_names: Iterable[str]) -> None:
55 """Deregister the specified instances from EnderChest, replacing all
56 instance symlinks with their actual targets, and then removing those
57 instances from the enderchest.cfg
59 Parameters
60 ----------
61 minecraft_root : Path
62 The root directory that your minecraft stuff (or, at least, the one
63 that's the parent of your EnderChest folder)
64 instance_names : list of str
65 The names of the instances to break
66 """
67 ender_chest = load_ender_chest(minecraft_root)
69 instance_lookup = {instance.name: instance for instance in ender_chest.instances}
70 instances: list[InstanceSpec] = []
71 for name in instance_names:
72 try:
73 instances.append(instance_lookup[name])
74 except KeyError:
75 BREAK_LOGGER.warning(
76 f'No instance named "%s" is registered to this EnderChest.'
77 "\nSkipping.",
78 name,
79 )
80 if len(instances) == 0:
81 BREAK_LOGGER.error("No valid instances specified.\nAborting.")
82 return
84 BREAK_LOGGER.warning(
85 "Are you sure you want to remove the following instances from your EnderChest?"
86 "\n%s\nDoing so will replace ALL the symlinks in each of the above instances"
87 "\nwith copies of their EnderChest-linked targets."
88 "\n\nTHIS CANNOT EASILY BE UNDONE!!",
89 "\n".join((f" - {instance.name}" for instance in instances)),
90 )
91 if not confirm(default=False):
92 BREAK_LOGGER.error("Aborting.")
93 return
95 _break(fs.ender_chest_folder(minecraft_root), instances)
96 for instance in instances:
97 ender_chest._instances.remove(instance)
98 create_ender_chest(minecraft_root, ender_chest)
101def _break(
102 chest_folder: Path,
103 instances: Iterable[InstanceSpec],
104) -> None:
105 """Actually perform the uninstallation
107 Parameters
108 ----------
109 chest_folder : Path
110 The path of the EnderChest folder that's being "broken"
111 instances : list of InstanceSpec
112 The instances to clear of EnderChest links
113 """
114 chest_folder = chest_folder.expanduser().resolve()
116 for instance in instances:
117 BREAK_LOGGER.info("Copying files into %s", instance.name)
118 for resource_path in instance.root.expanduser().rglob("*"):
119 if not resource_path.is_symlink():
120 continue
122 literal_target = resource_path.readlink()
124 direct_target = Path(
125 os.path.normpath(resource_path.readlink().expanduser().absolute())
126 )
128 final_target = resource_path.resolve().expanduser()
130 if not direct_target.is_relative_to(
131 chest_folder
132 ) and not final_target.is_relative_to(chest_folder):
133 # note: there's a pathological case where someone does something
134 # silly like:
135 # ~/.minecraft/options.txt -> ~/options.txt
136 # -> EnderChest/options.txt -> /configs/minecraft_options.txt
137 # where deleting your EnderChest folder would break the chain,
138 # but EnderChest wouldn't have ever created that chain, so that
139 # person is on their own.
140 continue
142 try:
143 resource_path.unlink()
144 BREAK_LOGGER.debug(
145 "Removed link: %s -> %s", resource_path, literal_target
146 )
147 if final_target.is_relative_to(chest_folder):
148 if final_target.is_dir():
149 shutil.copytree(
150 final_target,
151 resource_path,
152 symlinks=True,
153 dirs_exist_ok=True,
154 )
155 else:
156 shutil.copy2(
157 final_target,
158 resource_path,
159 follow_symlinks=False,
160 )
161 else:
162 resource_path.symlink_to(final_target)
164 BREAK_LOGGER.debug("Copied %s to %s", final_target, resource_path)
165 except OSError as copy_fail:
166 BREAK_LOGGER.warning(
167 "Failed to copy %s to %s:\n %s",
168 final_target,
169 resource_path,
170 copy_fail,
171 )