Split off highly version dependent code from SettingsRegistry
This commit is contained in:
@@ -202,9 +202,7 @@ public class ReplayMod implements
|
|||||||
modules.add(new ReplayModExtras(this));
|
modules.add(new ReplayModExtras(this));
|
||||||
modules.add(new ReplayModCompat());
|
modules.add(new ReplayModCompat());
|
||||||
|
|
||||||
//#if MC>=11400
|
|
||||||
settingsRegistry.register();
|
settingsRegistry.register();
|
||||||
//#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//#if MC<=11400
|
//#if MC<=11400
|
||||||
@@ -212,7 +210,7 @@ public class ReplayMod implements
|
|||||||
//$$ public void init(FMLPreInitializationEvent event) {
|
//$$ public void init(FMLPreInitializationEvent event) {
|
||||||
//$$ config = new Configuration(event.getSuggestedConfigurationFile());
|
//$$ config = new Configuration(event.getSuggestedConfigurationFile());
|
||||||
//$$ config.load();
|
//$$ config.load();
|
||||||
//$$ settingsRegistry.setConfiguration(config);
|
//$$ settingsRegistry.backend.setConfiguration(config);
|
||||||
//$$ settingsRegistry.save(); // Save default values to disk
|
//$$ settingsRegistry.save(); // Save default values to disk
|
||||||
//$$ }
|
//$$ }
|
||||||
//#endif
|
//#endif
|
||||||
|
|||||||
@@ -1,130 +1,18 @@
|
|||||||
package com.replaymod.core;
|
package com.replaymod.core;
|
||||||
|
|
||||||
import com.replaymod.core.events.SettingsChangedCallback;
|
import com.replaymod.core.events.SettingsChangedCallback;
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
//#if MC>=11400
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
//#else
|
|
||||||
//#if MC>=11400
|
|
||||||
//$$ import net.minecraftforge.common.ForgeConfigSpec;
|
|
||||||
//$$ import net.minecraftforge.fml.ModLoadingContext;
|
|
||||||
//$$ import net.minecraftforge.fml.config.ModConfig;
|
|
||||||
//$$ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
|
||||||
//#else
|
|
||||||
//$$ import net.minecraftforge.common.config.Configuration;
|
|
||||||
//#endif
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
import static com.replaymod.core.versions.MCVer.*;
|
|
||||||
|
|
||||||
public class SettingsRegistry {
|
public class SettingsRegistry {
|
||||||
private static Logger LOGGER = LogManager.getLogger();
|
private final Map<SettingKey<?>, Object> settings = Collections.synchronizedMap(new LinkedHashMap<>());
|
||||||
private Map<SettingKey<?>, Object> settings = Collections.synchronizedMap(new LinkedHashMap<>());
|
final SettingsRegistryBackend backend = new SettingsRegistryBackend(settings);
|
||||||
//#if MC>=11400
|
|
||||||
private final Path configFile = getMinecraft().runDirectory.toPath().resolve("config/replaymod.json");
|
|
||||||
//#else
|
|
||||||
//$$ private static final Object NULL_OBJECT = new Object();
|
|
||||||
//#if MC>=11400
|
|
||||||
//$$ private ForgeConfigSpec spec;
|
|
||||||
//$$ private ModConfig config;
|
|
||||||
//#else
|
|
||||||
//$$ private Configuration configuration;
|
|
||||||
//#endif
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
//#if MC>=11400
|
|
||||||
public void register() {
|
public void register() {
|
||||||
String config;
|
backend.register();
|
||||||
if (Files.exists(configFile)) {
|
|
||||||
try {
|
|
||||||
config = new String(Files.readAllBytes(configFile), StandardCharsets.UTF_8);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
save();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Gson gson = new Gson();
|
|
||||||
JsonObject root = gson.fromJson(config, JsonObject.class);
|
|
||||||
if (root == null) {
|
|
||||||
LOGGER.error("Config file {} appears corrupted: {}", configFile, config);
|
|
||||||
save();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (Map.Entry<SettingKey<?>, Object> entry : settings.entrySet()) {
|
|
||||||
SettingKey<?> key = entry.getKey();
|
|
||||||
JsonElement category = root.get(key.getCategory());
|
|
||||||
if (category != null && category.isJsonObject()) {
|
|
||||||
JsonElement valueElem = category.getAsJsonObject().get(key.getKey());
|
|
||||||
if (valueElem == null || !valueElem.isJsonPrimitive()) continue;
|
|
||||||
JsonPrimitive value = valueElem.getAsJsonPrimitive();
|
|
||||||
if (key.getDefault() instanceof Boolean && value.isBoolean()) {
|
|
||||||
entry.setValue(value.getAsBoolean());
|
|
||||||
}
|
|
||||||
if (key.getDefault() instanceof Integer && value.isNumber()) {
|
|
||||||
entry.setValue(value.getAsNumber().intValue());
|
|
||||||
}
|
|
||||||
if (key.getDefault() instanceof Double && value.isNumber()) {
|
|
||||||
entry.setValue(value.getAsNumber().doubleValue());
|
|
||||||
}
|
|
||||||
if (key.getDefault() instanceof String && value.isString()) {
|
|
||||||
entry.setValue(value.getAsString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//#else
|
|
||||||
//#if MC>=11400
|
|
||||||
//$$ public void register() {
|
|
||||||
//$$ if (spec == null) {
|
|
||||||
//$$ ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder();
|
|
||||||
//$$ for (SettingKey<?> key : settings.keySet()) {
|
|
||||||
//$$ builder
|
|
||||||
//$$ .translation(key.getDisplayString())
|
|
||||||
//$$ .define(key.getCategory() + "." + key.getKey(), key.getDefault());
|
|
||||||
//$$ }
|
|
||||||
//$$ spec = builder.build();
|
|
||||||
//$$ }
|
|
||||||
//$$ FMLJavaModLoadingContext.get().getModEventBus().addListener(this::load);
|
|
||||||
//$$ ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, spec);
|
|
||||||
//$$ }
|
|
||||||
//$$
|
|
||||||
//$$ private void load(ModConfig.Loading event) {
|
|
||||||
//$$ config = event.getConfig();
|
|
||||||
//$$ for (Map.Entry<SettingKey<?>, Object> entry : settings.entrySet()) {
|
|
||||||
//$$ SettingKey<?> key = entry.getKey();
|
|
||||||
//$$ Object value = config.getConfigData().get(key.getCategory() + "." + key.getKey());
|
|
||||||
//$$ entry.setValue(value == null ? key.getDefault() : value);
|
|
||||||
//$$ }
|
|
||||||
//$$ }
|
|
||||||
//#else
|
|
||||||
//$$ public void setConfiguration(Configuration configuration) {
|
|
||||||
//$$ this.configuration = configuration;
|
|
||||||
//$$
|
|
||||||
//$$ List<SettingKey<?>> keys = new ArrayList<>(settings.keySet());
|
|
||||||
//$$ settings.clear();
|
|
||||||
//$$ for (SettingKey key : keys) {
|
|
||||||
//$$ register(key);
|
|
||||||
//$$ }
|
|
||||||
//$$ }
|
|
||||||
//#endif
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
public void register(Class<?> settingsClass) {
|
public void register(Class<?> settingsClass) {
|
||||||
for (Field field : settingsClass.getDeclaredFields()) {
|
for (Field field : settingsClass.getDeclaredFields()) {
|
||||||
@@ -140,34 +28,8 @@ public class SettingsRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void register(SettingKey<?> key) {
|
public void register(SettingKey<?> key) {
|
||||||
//#if MC>=11400
|
|
||||||
settings.put(key, key.getDefault());
|
settings.put(key, key.getDefault());
|
||||||
//#else
|
backend.register(key);
|
||||||
//#if MC>=11400
|
|
||||||
//$$ if (spec != null) {
|
|
||||||
//$$ throw new IllegalStateException("Cannot register more settings are spec has been built.");
|
|
||||||
//$$ }
|
|
||||||
//$$ settings.put(key, NULL_OBJECT);
|
|
||||||
//#else
|
|
||||||
//$$ Object value;
|
|
||||||
//$$ if (configuration != null) {
|
|
||||||
//$$ if (key.getDefault() instanceof Boolean) {
|
|
||||||
//$$ value = configuration.get(key.getCategory(), key.getKey(), (Boolean) key.getDefault()).getBoolean();
|
|
||||||
//$$ } else if (key.getDefault() instanceof Integer) {
|
|
||||||
//$$ value = configuration.get(key.getCategory(), key.getKey(), (Integer) key.getDefault()).getInt();
|
|
||||||
//$$ } else if (key.getDefault() instanceof Double) {
|
|
||||||
//$$ value = configuration.get(key.getCategory(), key.getKey(), (Double) key.getDefault()).getDouble();
|
|
||||||
//$$ } else if (key.getDefault() instanceof String) {
|
|
||||||
//$$ value = configuration.get(key.getCategory(), key.getKey(), (String) key.getDefault()).getString();
|
|
||||||
//$$ } else {
|
|
||||||
//$$ throw new IllegalArgumentException("Default type " + key.getDefault().getClass() + " not supported.");
|
|
||||||
//$$ }
|
|
||||||
//$$ } else {
|
|
||||||
//$$ value = NULL_OBJECT;
|
|
||||||
//$$ }
|
|
||||||
//$$ settings.put(key, value);
|
|
||||||
//#endif
|
|
||||||
//#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<SettingKey<?>> getSettings() {
|
public Set<SettingKey<?>> getSettings() {
|
||||||
@@ -183,68 +45,13 @@ public class SettingsRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public <T> void set(SettingKey<T> key, T value) {
|
public <T> void set(SettingKey<T> key, T value) {
|
||||||
//#if MC<11400
|
backend.update(key, value);
|
||||||
//#if MC>=11400
|
|
||||||
//$$ if (config != null) {
|
|
||||||
//$$ config.getConfigData().set(key.getCategory() + "." + key.getKey(), value);
|
|
||||||
//$$ }
|
|
||||||
//#else
|
|
||||||
//$$ if (key.getDefault() instanceof Boolean) {
|
|
||||||
//$$ configuration.get(key.getCategory(), key.getKey(), (Boolean) key.getDefault()).set((Boolean) value);
|
|
||||||
//$$ } else if (key.getDefault() instanceof Integer) {
|
|
||||||
//$$ configuration.get(key.getCategory(), key.getKey(), (Integer) key.getDefault()).set((Integer) value);
|
|
||||||
//$$ } else if (key.getDefault() instanceof Double) {
|
|
||||||
//$$ configuration.get(key.getCategory(), key.getKey(), (Double) key.getDefault()).set((Double) value);
|
|
||||||
//$$ } else if (key.getDefault() instanceof String) {
|
|
||||||
//$$ configuration.get(key.getCategory(), key.getKey(), (String) key.getDefault()).set((String) value);
|
|
||||||
//$$ } else {
|
|
||||||
//$$ throw new IllegalArgumentException("Default type " + key.getDefault().getClass() + " not supported.");
|
|
||||||
//$$ }
|
|
||||||
//#endif
|
|
||||||
//#endif
|
|
||||||
settings.put(key, value);
|
settings.put(key, value);
|
||||||
SettingsChangedCallback.EVENT.invoker().onSettingsChanged(this, key);
|
SettingsChangedCallback.EVENT.invoker().onSettingsChanged(this, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save() {
|
public void save() {
|
||||||
//#if MC>=11400
|
backend.save();
|
||||||
JsonObject root = new JsonObject();
|
|
||||||
for (Map.Entry<SettingKey<?>, Object> entry : settings.entrySet()) {
|
|
||||||
SettingKey<?> key = entry.getKey();
|
|
||||||
JsonObject category = root.getAsJsonObject(key.getCategory());
|
|
||||||
if (category == null) {
|
|
||||||
category = new JsonObject();
|
|
||||||
root.add(key.getCategory(), category);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object value = entry.getValue();
|
|
||||||
if (value instanceof Boolean) {
|
|
||||||
category.addProperty(key.getKey(), (Boolean) value);
|
|
||||||
}
|
|
||||||
if (value instanceof Number) {
|
|
||||||
category.addProperty(key.getKey(), (Number) value);
|
|
||||||
}
|
|
||||||
if (value instanceof String) {
|
|
||||||
category.addProperty(key.getKey(), (String) value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
|
||||||
String config = gson.toJson(root);
|
|
||||||
try {
|
|
||||||
Files.createDirectories(configFile.getParent());
|
|
||||||
Files.write(configFile, config.getBytes(StandardCharsets.UTF_8));
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
//#else
|
|
||||||
//#if MC>=11400
|
|
||||||
//$$ if (config != null) {
|
|
||||||
//$$ config.save();
|
|
||||||
//$$ }
|
|
||||||
//#else
|
|
||||||
//$$ configuration.save();
|
|
||||||
//#endif
|
|
||||||
//#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface SettingKey<T> {
|
public interface SettingKey<T> {
|
||||||
|
|||||||
110
src/main/java/com/replaymod/core/SettingsRegistryBackend.java
Normal file
110
src/main/java/com/replaymod/core/SettingsRegistryBackend.java
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package com.replaymod.core;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.replaymod.core.versions.MCVer.getMinecraft;
|
||||||
|
|
||||||
|
class SettingsRegistryBackend {
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger();
|
||||||
|
private final Map<SettingsRegistry.SettingKey<?>, Object> settings;
|
||||||
|
|
||||||
|
private final Path configFile = getMinecraft().runDirectory.toPath().resolve("config/replaymod.json");
|
||||||
|
|
||||||
|
SettingsRegistryBackend(Map<SettingsRegistry.SettingKey<?>, Object> settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register() {
|
||||||
|
String config;
|
||||||
|
if (Files.exists(configFile)) {
|
||||||
|
try {
|
||||||
|
config = new String(Files.readAllBytes(configFile), StandardCharsets.UTF_8);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Gson gson = new Gson();
|
||||||
|
JsonObject root = gson.fromJson(config, JsonObject.class);
|
||||||
|
if (root == null) {
|
||||||
|
LOGGER.error("Config file {} appears corrupted: {}", configFile, config);
|
||||||
|
save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Map.Entry<SettingsRegistry.SettingKey<?>, Object> entry : settings.entrySet()) {
|
||||||
|
SettingsRegistry.SettingKey<?> key = entry.getKey();
|
||||||
|
JsonElement category = root.get(key.getCategory());
|
||||||
|
if (category != null && category.isJsonObject()) {
|
||||||
|
JsonElement valueElem = category.getAsJsonObject().get(key.getKey());
|
||||||
|
if (valueElem == null || !valueElem.isJsonPrimitive()) continue;
|
||||||
|
JsonPrimitive value = valueElem.getAsJsonPrimitive();
|
||||||
|
if (key.getDefault() instanceof Boolean && value.isBoolean()) {
|
||||||
|
entry.setValue(value.getAsBoolean());
|
||||||
|
}
|
||||||
|
if (key.getDefault() instanceof Integer && value.isNumber()) {
|
||||||
|
entry.setValue(value.getAsNumber().intValue());
|
||||||
|
}
|
||||||
|
if (key.getDefault() instanceof Double && value.isNumber()) {
|
||||||
|
entry.setValue(value.getAsNumber().doubleValue());
|
||||||
|
}
|
||||||
|
if (key.getDefault() instanceof String && value.isString()) {
|
||||||
|
entry.setValue(value.getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public void register(SettingsRegistry.SettingKey<?> key) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public <T> void update(SettingsRegistry.SettingKey<T> key, T value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
JsonObject root = new JsonObject();
|
||||||
|
for (Map.Entry<SettingsRegistry.SettingKey<?>, Object> entry : settings.entrySet()) {
|
||||||
|
SettingsRegistry.SettingKey<?> key = entry.getKey();
|
||||||
|
JsonObject category = root.getAsJsonObject(key.getCategory());
|
||||||
|
if (category == null) {
|
||||||
|
category = new JsonObject();
|
||||||
|
root.add(key.getCategory(), category);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object value = entry.getValue();
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
category.addProperty(key.getKey(), (Boolean) value);
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
category.addProperty(key.getKey(), (Number) value);
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
category.addProperty(key.getKey(), (String) value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
String config = gson.toJson(root);
|
||||||
|
try {
|
||||||
|
Files.createDirectories(configFile.getParent());
|
||||||
|
Files.write(configFile, config.getBytes(StandardCharsets.UTF_8));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.replaymod.core;
|
||||||
|
|
||||||
|
import net.minecraftforge.common.config.Configuration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class SettingsRegistryBackend {
|
||||||
|
private final Map<SettingsRegistry.SettingKey<?>, Object> settings;
|
||||||
|
|
||||||
|
private static final Object NULL_OBJECT = new Object();
|
||||||
|
private Configuration configuration;
|
||||||
|
|
||||||
|
SettingsRegistryBackend(Map<SettingsRegistry.SettingKey<?>, Object> settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfiguration(Configuration configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
|
||||||
|
for (SettingsRegistry.SettingKey<?> key : new ArrayList<>(settings.keySet())) {
|
||||||
|
register(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(SettingsRegistry.SettingKey<?> key) {
|
||||||
|
Object value;
|
||||||
|
if (configuration != null) {
|
||||||
|
if (key.getDefault() instanceof Boolean) {
|
||||||
|
value = configuration.get(key.getCategory(), key.getKey(), (Boolean) key.getDefault()).getBoolean();
|
||||||
|
} else if (key.getDefault() instanceof Integer) {
|
||||||
|
value = configuration.get(key.getCategory(), key.getKey(), (Integer) key.getDefault()).getInt();
|
||||||
|
} else if (key.getDefault() instanceof Double) {
|
||||||
|
value = configuration.get(key.getCategory(), key.getKey(), (Double) key.getDefault()).getDouble();
|
||||||
|
} else if (key.getDefault() instanceof String) {
|
||||||
|
value = configuration.get(key.getCategory(), key.getKey(), (String) key.getDefault()).getString();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Default type " + key.getDefault().getClass() + " not supported.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = NULL_OBJECT;
|
||||||
|
}
|
||||||
|
settings.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void update(SettingsRegistry.SettingKey<T> key, T value) {
|
||||||
|
if (key.getDefault() instanceof Boolean) {
|
||||||
|
configuration.get(key.getCategory(), key.getKey(), (Boolean) key.getDefault()).set((Boolean) value);
|
||||||
|
} else if (key.getDefault() instanceof Integer) {
|
||||||
|
configuration.get(key.getCategory(), key.getKey(), (Integer) key.getDefault()).set((Integer) value);
|
||||||
|
} else if (key.getDefault() instanceof Double) {
|
||||||
|
configuration.get(key.getCategory(), key.getKey(), (Double) key.getDefault()).set((Double) value);
|
||||||
|
} else if (key.getDefault() instanceof String) {
|
||||||
|
configuration.get(key.getCategory(), key.getKey(), (String) key.getDefault()).set((String) value);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Default type " + key.getDefault().getClass() + " not supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
configuration.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user