Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MinecraftForge
GitHub Repository: MinecraftForge/MinecraftForge
Path: blob/1.21.x/fmlcore/src/main/java/net/minecraftforge/fml/config/ConfigFileTypeHandler.java
7350 views
1
/*
2
* Copyright (c) Forge Development LLC and contributors
3
* SPDX-License-Identifier: LGPL-2.1-only
4
*/
5
6
package net.minecraftforge.fml.config;
7
8
import com.electronwill.nightconfig.core.ConfigFormat;
9
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
10
import com.electronwill.nightconfig.core.file.FileWatcher;
11
import com.electronwill.nightconfig.core.io.ParsingException;
12
import com.electronwill.nightconfig.core.io.WritingMode;
13
import com.mojang.logging.LogUtils;
14
import net.minecraftforge.fml.loading.FMLConfig;
15
import net.minecraftforge.fml.loading.FMLPaths;
16
import org.apache.commons.io.FilenameUtils;
17
import org.jetbrains.annotations.Nullable;
18
import org.slf4j.Logger;
19
20
import java.io.IOException;
21
import java.nio.file.Files;
22
import java.nio.file.Path;
23
import java.util.function.Function;
24
25
import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
26
27
public class ConfigFileTypeHandler {
28
private static final Logger LOGGER = LogUtils.getLogger();
29
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.getConfigValue(FMLConfig.ConfigValue.DEFAULT_CONFIG_PATH));
30
31
private static final ConfigFileTypeHandler CLIENT = new ConfigFileTypeHandler(ModConfig.Type.CLIENT);
32
private static final ConfigFileTypeHandler COMMON = new ConfigFileTypeHandler(ModConfig.Type.COMMON);
33
private static final ConfigFileTypeHandler SERVER = new ConfigFileTypeHandler(ModConfig.Type.SERVER);
34
35
private final @Nullable ModConfig.Type type;
36
private @Nullable FileWatcher watcher;
37
38
// exists for bin compat
39
public ConfigFileTypeHandler() {
40
this(null);
41
}
42
43
private ConfigFileTypeHandler(@Nullable ModConfig.Type type) {
44
this.type = type;
45
}
46
47
/**
48
* Gets the handler for the given {@link ModConfig.Type}.
49
*
50
* @param type The type to get the handler for
51
* @return The handler
52
*/
53
static ConfigFileTypeHandler get(ModConfig.Type type) {
54
return switch (type) {
55
case CLIENT -> CLIENT;
56
case COMMON -> COMMON;
57
case SERVER -> SERVER;
58
};
59
}
60
61
/**
62
* Gets the {@link FileWatcher} for this handler, creating it if it doesn't exist and/or has been stopped.
63
*
64
* @return The watcher
65
*
66
* @apiNote This is package-private so modders can't just call {@link FileWatcher#stop()} and fuck up everything.
67
*/
68
FileWatcher getWatcher() {
69
if (this.watcher == null) {
70
LOGGER.debug(CONFIG, "Starting watcher for handler: {}", this);
71
this.watcher = new FileWatcher();
72
}
73
74
return this.watcher;
75
}
76
77
/**
78
* Stops the {@link FileWatcher} for this handler, and sets it to null afterward. Use this instead of
79
* {@link FileWatcher#stop()}.
80
*/
81
void stopWatcher() {
82
if (this.watcher == null) return;
83
84
LOGGER.debug(CONFIG, "Stopping watcher for hander: {}", this);
85
this.watcher.stop();
86
this.watcher = null;
87
}
88
89
public Function<ModConfig, CommentedFileConfig> reader(Path configBasePath) {
90
return (c) -> {
91
final Path configPath = configBasePath.resolve(c.getFileName());
92
final CommentedFileConfig configData = CommentedFileConfig.builder(configPath).sync().
93
preserveInsertionOrder().
94
autosave().
95
onFileNotFound((newfile, configFormat)-> setupConfigFile(c, newfile, configFormat)).
96
writingMode(WritingMode.REPLACE).
97
build();
98
LOGGER.debug(CONFIG, "Built TOML config for {}", configPath);
99
try
100
{
101
configData.load();
102
}
103
catch (ParsingException ex)
104
{
105
throw new ConfigLoadingException(c, ex);
106
}
107
LOGGER.debug(CONFIG, "Loaded TOML config file {}", configPath);
108
this.getWatcher().addWatch(configPath, new ConfigWatcher(c, configData, Thread.currentThread().getContextClassLoader()));
109
LOGGER.debug(CONFIG, "Watching TOML config file {} for changes", configPath);
110
return configData;
111
};
112
}
113
114
public void unload(Path configBasePath, ModConfig config) {
115
Path configPath = configBasePath.resolve(config.getFileName());
116
try {
117
this.getWatcher().removeWatch(configPath);
118
} catch (RuntimeException e) {
119
LOGGER.error("Failed to remove config {} from tracker!", configPath, e);
120
}
121
}
122
123
private static boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
124
if (!Files.isDirectory(file.getParent())) {
125
Files.createDirectories(file.getParent());
126
}
127
Path p = defaultConfigPath.resolve(modConfig.getFileName());
128
if (Files.exists(p)) {
129
LOGGER.info(CONFIG, "Loading default config file from path {}", p);
130
Files.copy(p, file);
131
} else {
132
Files.createFile(file);
133
conf.initEmptyFile(file);
134
}
135
return true;
136
}
137
138
public static void backUpConfig(final CommentedFileConfig commentedFileConfig)
139
{
140
backUpConfig(commentedFileConfig, 5); //TODO: Think of a way for mods to set their own preference (include a sanity check as well, no disk stuffing)
141
}
142
143
public static void backUpConfig(final CommentedFileConfig commentedFileConfig, final int maxBackups)
144
{
145
Path bakFileLocation = commentedFileConfig.getNioPath().getParent();
146
String bakFileName = FilenameUtils.removeExtension(commentedFileConfig.getFile().getName());
147
String bakFileExtension = FilenameUtils.getExtension(commentedFileConfig.getFile().getName()) + ".bak";
148
Path bakFile = bakFileLocation.resolve(bakFileName + "-1" + "." + bakFileExtension);
149
try
150
{
151
for(int i = maxBackups; i > 0; i--)
152
{
153
Path oldBak = bakFileLocation.resolve(bakFileName + "-" + i + "." + bakFileExtension);
154
if(Files.exists(oldBak))
155
{
156
if(i >= maxBackups)
157
Files.delete(oldBak);
158
else
159
Files.move(oldBak, bakFileLocation.resolve(bakFileName + "-" + (i + 1) + "." + bakFileExtension));
160
}
161
}
162
Files.copy(commentedFileConfig.getNioPath(), bakFile);
163
}
164
catch (IOException exception)
165
{
166
LOGGER.warn(CONFIG, "Failed to back up config file {}", commentedFileConfig.getNioPath(), exception);
167
}
168
}
169
170
@Override
171
public String toString() {
172
return "ConfigFileTypeHandler[" + (type != null ? type : "UNKNOWN") + "]";
173
}
174
175
private record ConfigWatcher(
176
ModConfig modConfig,
177
CommentedFileConfig commentedFileConfig,
178
ClassLoader realClassLoader
179
) implements Runnable {
180
@Override
181
public void run() {
182
// Force the regular classloader onto the special thread
183
Thread.currentThread().setContextClassLoader(realClassLoader);
184
if (!this.modConfig.getSpec().isCorrecting()) {
185
try
186
{
187
this.commentedFileConfig.load();
188
if(!this.modConfig.getSpec().isCorrect(commentedFileConfig))
189
{
190
LOGGER.warn(CONFIG, "Configuration file {} is not correct. Correcting", commentedFileConfig.getFile().getAbsolutePath());
191
ConfigFileTypeHandler.backUpConfig(commentedFileConfig);
192
this.modConfig.getSpec().correct(commentedFileConfig);
193
commentedFileConfig.save();
194
}
195
}
196
catch (ParsingException ex)
197
{
198
throw new ConfigLoadingException(modConfig, ex);
199
}
200
LOGGER.debug(CONFIG, "Config file {} changed, sending notifies", this.modConfig.getFileName());
201
this.modConfig.getSpec().afterReload();
202
this.modConfig.fireEvent(IConfigEvent.reloading(this.modConfig));
203
}
204
}
205
}
206
207
private static class ConfigLoadingException extends RuntimeException
208
{
209
public ConfigLoadingException(ModConfig config, Exception cause)
210
{
211
super("Failed loading config file " + config.getFileName() + " of type " + config.getType() + " for modid " + config.getModId(), cause);
212
}
213
}
214
}
215
216