refactor: gracefully handle missing data deps

feat/data-edit-commands
William 9 months ago
parent e0b81e4c76
commit 4fa7106a46
No known key found for this signature in database

@ -82,33 +82,33 @@ public class BukkitLegacyConverter extends LegacyConverter {
final JSONObject status = object.getJSONObject("status_data"); final JSONObject status = object.getJSONObject("status_data");
final HashMap<Identifier, Data> containers = Maps.newHashMap(); final HashMap<Identifier, Data> containers = Maps.newHashMap();
if (shouldImport(Identifier.HEALTH)) { if (Identifier.HEALTH.isEnabled()) {
containers.put(Identifier.HEALTH, BukkitData.Health.from( containers.put(Identifier.HEALTH, BukkitData.Health.from(
status.getDouble("health"), status.getDouble("health"),
status.getDouble("health_scale"), status.getDouble("health_scale"),
false false
)); ));
} }
if (shouldImport(Identifier.HUNGER)) { if (Identifier.HUNGER.isEnabled()) {
containers.put(Identifier.HUNGER, BukkitData.Hunger.from( containers.put(Identifier.HUNGER, BukkitData.Hunger.from(
status.getInt("hunger"), status.getInt("hunger"),
status.getFloat("saturation"), status.getFloat("saturation"),
status.getFloat("saturation_exhaustion") status.getFloat("saturation_exhaustion")
)); ));
} }
if (shouldImport(Identifier.EXPERIENCE)) { if (Identifier.EXPERIENCE.isEnabled()) {
containers.put(Identifier.EXPERIENCE, BukkitData.Experience.from( containers.put(Identifier.EXPERIENCE, BukkitData.Experience.from(
status.getInt("total_experience"), status.getInt("total_experience"),
status.getInt("experience_level"), status.getInt("experience_level"),
status.getFloat("experience_progress") status.getFloat("experience_progress")
)); ));
} }
if (shouldImport(Identifier.GAME_MODE)) { if (Identifier.GAME_MODE.isEnabled()) {
containers.put(Identifier.GAME_MODE, BukkitData.GameMode.from( containers.put(Identifier.GAME_MODE, BukkitData.GameMode.from(
status.getString("game_mode") status.getString("game_mode")
)); ));
} }
if (shouldImport(Identifier.FLIGHT_STATUS)) { if (Identifier.FLIGHT_STATUS.isEnabled()) {
containers.put(Identifier.FLIGHT_STATUS, BukkitData.FlightStatus.from( containers.put(Identifier.FLIGHT_STATUS, BukkitData.FlightStatus.from(
status.getBoolean("is_flying"), status.getBoolean("is_flying"),
status.getBoolean("is_flying") status.getBoolean("is_flying")
@ -119,7 +119,7 @@ public class BukkitLegacyConverter extends LegacyConverter {
@NotNull @NotNull
private Optional<Data.Items.Inventory> readInventory(@NotNull JSONObject object) { private Optional<Data.Items.Inventory> readInventory(@NotNull JSONObject object) {
if (!object.has("inventory") || !shouldImport(Identifier.INVENTORY)) { if (!object.has("inventory") || !Identifier.INVENTORY.isEnabled()) {
return Optional.empty(); return Optional.empty();
} }
@ -131,7 +131,7 @@ public class BukkitLegacyConverter extends LegacyConverter {
@NotNull @NotNull
private Optional<Data.Items.EnderChest> readEnderChest(@NotNull JSONObject object) { private Optional<Data.Items.EnderChest> readEnderChest(@NotNull JSONObject object) {
if (!object.has("ender_chest") || !shouldImport(Identifier.ENDER_CHEST)) { if (!object.has("ender_chest") || !Identifier.ENDER_CHEST.isEnabled()) {
return Optional.empty(); return Optional.empty();
} }
@ -143,7 +143,7 @@ public class BukkitLegacyConverter extends LegacyConverter {
@NotNull @NotNull
private Optional<Data.Location> readLocation(@NotNull JSONObject object) { private Optional<Data.Location> readLocation(@NotNull JSONObject object) {
if (!object.has("location") || !shouldImport(Identifier.LOCATION)) { if (!object.has("location") || !Identifier.LOCATION.isEnabled()) {
return Optional.empty(); return Optional.empty();
} }
@ -164,7 +164,7 @@ public class BukkitLegacyConverter extends LegacyConverter {
@NotNull @NotNull
private Optional<Data.Advancements> readAdvancements(@NotNull JSONObject object) { private Optional<Data.Advancements> readAdvancements(@NotNull JSONObject object) {
if (!object.has("advancements") || !shouldImport(Identifier.ADVANCEMENTS)) { if (!object.has("advancements") || !Identifier.ADVANCEMENTS.isEnabled()) {
return Optional.empty(); return Optional.empty();
} }
@ -187,7 +187,7 @@ public class BukkitLegacyConverter extends LegacyConverter {
@NotNull @NotNull
private Optional<Data.Statistics> readStatistics(@NotNull JSONObject object) { private Optional<Data.Statistics> readStatistics(@NotNull JSONObject object) {
if (!object.has("statistics") || !shouldImport(Identifier.STATISTICS)) { if (!object.has("statistics") || !Identifier.STATISTICS.isEnabled()) {
return Optional.empty(); return Optional.empty();
} }
@ -281,11 +281,6 @@ public class BukkitLegacyConverter extends LegacyConverter {
return serializedItemStack != null ? ItemStack.deserialize((Map<String, Object>) serializedItemStack) : null; return serializedItemStack != null ? ItemStack.deserialize((Map<String, Object>) serializedItemStack) : null;
} }
private boolean shouldImport(@NotNull Identifier type) {
return plugin.getSettings().getSynchronization().isFeatureEnabled(type);
}
@NotNull @NotNull
private Date parseDate(@NotNull String dateString) { private Date parseDate(@NotNull String dateString) {
try { try {

@ -239,13 +239,11 @@ public class HuskSyncCommand extends Command implements TabProvider {
)), )),
DATA_TYPES(plugin -> Component.join( DATA_TYPES(plugin -> Component.join(
JoinConfiguration.commas(true), JoinConfiguration.commas(true),
plugin.getRegisteredDataTypes().stream().map(i -> { plugin.getRegisteredDataTypes().stream().map(i -> Component.textOfChildren(Component.text(i.toString())
boolean enabled = plugin.getSettings().getSynchronization().isFeatureEnabled(i); .appendSpace().append(Component.text(i.isEnabled() ? '✔' : '❌')))
return Component.textOfChildren(Component.text(i.toString()) .color(i.isEnabled() ? NamedTextColor.GREEN : NamedTextColor.RED)
.appendSpace().append(Component.text(enabled ? '✔' : '❌')))
.color(enabled ? NamedTextColor.GREEN : NamedTextColor.RED)
.hoverEvent(HoverEvent.showText( .hoverEvent(HoverEvent.showText(
Component.text(enabled ? "Enabled" : "Disabled") Component.text(i.isEnabled() ? "Enabled" : "Disabled")
.append(Component.newline()) .append(Component.newline())
.append(Component.text("Dependencies: %s".formatted(i.getDependencies() .append(Component.text("Dependencies: %s".formatted(i.getDependencies()
.isEmpty() ? "(None)" : i.getDependencies().stream() .isEmpty() ? "(None)" : i.getDependencies().stream()
@ -253,8 +251,7 @@ public class HuskSyncCommand extends Command implements TabProvider {
d.getKey().value(), d.isRequired() ? "Required" : "Optional" d.getKey().value(), d.isRequired() ? "Required" : "Optional"
)).collect(Collectors.joining(", "))) )).collect(Collectors.joining(", ")))
).color(NamedTextColor.GRAY)) ).color(NamedTextColor.GRAY))
)); ))).toList()
}).toList()
)); ));
private final Function<HuskSync, Component> supplier; private final Function<HuskSync, Component> supplier;

@ -19,10 +19,7 @@
package net.william278.husksync.data; package net.william278.husksync.data;
import lombok.AccessLevel; import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.kyori.adventure.key.InvalidKeyException; import net.kyori.adventure.key.InvalidKeyException;
import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Key;
import org.intellij.lang.annotations.Subst; import org.intellij.lang.annotations.Subst;
@ -73,10 +70,14 @@ public class Identifier {
private final boolean enabledByDefault; private final boolean enabledByDefault;
@Getter @Getter
private final Set<Dependency> dependencies; private final Set<Dependency> dependencies;
@Setter
@Getter
public boolean enabled;
private Identifier(@NotNull Key key, boolean enabledByDefault, @NotNull Set<Dependency> dependencies) { private Identifier(@NotNull Key key, boolean enabledByDefault, @NotNull Set<Dependency> dependencies) {
this.key = key; this.key = key;
this.enabledByDefault = enabledByDefault; this.enabledByDefault = enabledByDefault;
this.enabled = enabledByDefault;
this.dependencies = dependencies; this.dependencies = dependencies;
} }
@ -269,7 +270,7 @@ public class Identifier {
} }
/** /**
* Represents a data dependency of an identifier * Represents a data dependency of an identifier, used to determine the order in which data is applied to users
* *
* @since 3.5.4 * @since 3.5.4
*/ */

@ -44,17 +44,17 @@ public interface SerializerRegistry {
/** /**
* Register a data serializer for the given {@link Identifier} * Register a data serializer for the given {@link Identifier}
* *
* @param identifier the {@link Identifier} * @param id the {@link Identifier}
* @param serializer the {@link Serializer} * @param serializer the {@link Serializer}
* @since 3.0 * @since 3.0
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default void registerSerializer(@NotNull Identifier identifier, default void registerSerializer(@NotNull Identifier id, @NotNull Serializer<? extends Data> serializer) {
@NotNull Serializer<? extends Data> serializer) { if (id.isCustom()) {
if (identifier.isCustom()) { getPlugin().log(Level.INFO, "Registered custom data type: %s".formatted(id));
getPlugin().log(Level.INFO, "Registered custom data type: %s".formatted(identifier));
} }
getSerializers().put(identifier, (Serializer<Data>) serializer); id.setEnabled(id.isCustom() || getPlugin().getSettings().getSynchronization().isFeatureEnabled(id));
getSerializers().put(id, (Serializer<Data>) serializer);
} }
/** /**
@ -66,17 +66,16 @@ public interface SerializerRegistry {
* @since 3.5.4 * @since 3.5.4
*/ */
default void validateDependencies() throws IllegalStateException { default void validateDependencies() throws IllegalStateException {
getSerializers().keySet().stream().filter(this::isDataTypeEnabled) getSerializers().keySet().stream().filter(Identifier::isEnabled)
.forEach(identifier -> { .forEach(identifier -> {
final List<String> unmet = identifier.getDependencies().stream() final List<String> unmet = identifier.getDependencies().stream()
.filter(Identifier.Dependency::isRequired) .filter(Identifier.Dependency::isRequired)
.filter(dep -> !isDataTypeAvailable(dep.getKey().asString())) .filter(dep -> !isDataTypeAvailable(dep.getKey().asString()))
.map(dep -> dep.getKey().asString()).toList(); .map(dep -> dep.getKey().asString()).toList();
if (!unmet.isEmpty()) { if (!unmet.isEmpty()) {
throw new IllegalStateException( identifier.setEnabled(false);
"\"%s\" data requires the following disabled data types to facilitate syncing: %s" getPlugin().log(Level.WARNING, "Disabled %s syncing as the following types need to be on: %s"
.formatted(identifier, String.join(", ", unmet)) .formatted(identifier, String.join(", ", unmet)));
);
} }
}); });
} }
@ -148,12 +147,7 @@ public interface SerializerRegistry {
// Returns if a data type is available and enabled in the config // Returns if a data type is available and enabled in the config
private boolean isDataTypeAvailable(@NotNull String key) { private boolean isDataTypeAvailable(@NotNull String key) {
return getIdentifier(key).map(this::isDataTypeEnabled).orElse(false); return getIdentifier(key).map(Identifier::isEnabled).orElse(false);
}
// Returns if a data type is enabled in the config
private boolean isDataTypeEnabled(@NotNull Identifier identifier) {
return getPlugin().getSettings().getSynchronization().isFeatureEnabled(identifier);
} }
@NotNull @NotNull

@ -43,7 +43,7 @@ public interface UserDataHolder extends DataHolder {
@NotNull @NotNull
default Map<Identifier, Data> getData() { default Map<Identifier, Data> getData() {
return getPlugin().getRegisteredDataTypes().stream() return getPlugin().getRegisteredDataTypes().stream()
.filter(type -> type.isCustom() || getPlugin().getSettings().getSynchronization().isFeatureEnabled(type)) .filter(Identifier::isEnabled)
.map(id -> Map.entry(id, getData(id))) .map(id -> Map.entry(id, getData(id)))
.filter(data -> data.getValue().isPresent()) .filter(data -> data.getValue().isPresent())
.collect(HashMap::new, (map, data) -> map.put(data.getKey(), data.getValue().get()), HashMap::putAll); .collect(HashMap::new, (map, data) -> map.put(data.getKey(), data.getValue().get()), HashMap::putAll);
@ -79,7 +79,8 @@ public interface UserDataHolder extends DataHolder {
* Deserialize and apply a data snapshot to this data owner * Deserialize and apply a data snapshot to this data owner
* <p> * <p>
* This method will deserialize the data on the current thread, then synchronously apply it on * This method will deserialize the data on the current thread, then synchronously apply it on
* the main server thread. * the main server thread. The order data will be applied is determined based on the dependencies of
* each data type (see {@link Identifier.Dependency}).
* </p> * </p>
* The {@code runAfter} callback function will be run after the snapshot has been applied. * The {@code runAfter} callback function will be run after the snapshot has been applied.
* *
@ -106,13 +107,16 @@ public interface UserDataHolder extends DataHolder {
try { try {
for (Map.Entry<Identifier, Data> entry : unpacked.getData().entrySet()) { for (Map.Entry<Identifier, Data> entry : unpacked.getData().entrySet()) {
final Identifier identifier = entry.getKey(); final Identifier identifier = entry.getKey();
if (plugin.getSettings().getSynchronization().isFeatureEnabled(identifier)) { if (!identifier.isEnabled()) {
continue;
}
// Apply the identified data
if (identifier.isCustom()) { if (identifier.isCustom()) {
getCustomDataStore().put(identifier, entry.getValue()); getCustomDataStore().put(identifier, entry.getValue());
} }
entry.getValue().apply(this, plugin); entry.getValue().apply(this, plugin);
} }
}
} catch (Throwable e) { } catch (Throwable e) {
plugin.log(Level.SEVERE, String.format("Failed to apply data snapshot to %s", getUsername()), e); plugin.log(Level.SEVERE, String.format("Failed to apply data snapshot to %s", getUsername()), e);
plugin.runAsync(() -> runAfter.accept(false)); plugin.runAsync(() -> runAfter.accept(false));

Loading…
Cancel
Save