Fix wrong timestamp/UUID being used for legacy conversion (#167)

* Maintain legacy snapshot IDs when updating

* Also maintain timestamps during conversion

* Actually implement timestamp fix in LegacyConverter
feat/data-edit-commands
William 1 year ago committed by GitHub
parent 635edb930f
commit 7034a97d3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -39,6 +39,7 @@ import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.OffsetDateTime;
import java.util.*; import java.util.*;
public class BukkitLegacyConverter extends LegacyConverter { public class BukkitLegacyConverter extends LegacyConverter {
@ -49,7 +50,8 @@ public class BukkitLegacyConverter extends LegacyConverter {
@NotNull @NotNull
@Override @Override
public DataSnapshot.Packed convert(@NotNull byte[] data) throws DataAdapter.AdaptionException { public DataSnapshot.Packed convert(@NotNull byte[] data, @NotNull UUID id,
@NotNull OffsetDateTime timestamp) throws DataAdapter.AdaptionException {
final JSONObject object = new JSONObject(plugin.getDataAdapter().bytesToString(data)); final JSONObject object = new JSONObject(plugin.getDataAdapter().bytesToString(data));
final int version = object.getInt("format_version"); final int version = object.getInt("format_version");
if (version != 3) { if (version != 3) {
@ -61,6 +63,7 @@ public class BukkitLegacyConverter extends LegacyConverter {
// Read legacy data from the JSON object // Read legacy data from the JSON object
final DataSnapshot.Builder builder = DataSnapshot.builder(plugin) final DataSnapshot.Builder builder = DataSnapshot.builder(plugin)
.id(id).timestamp(timestamp)
.saveCause(DataSnapshot.SaveCause.CONVERTED_FROM_V2) .saveCause(DataSnapshot.SaveCause.CONVERTED_FROM_V2)
.data(readStatusData(object)); .data(readStatusData(object));
readInventory(object).ifPresent(builder::inventory); readInventory(object).ifPresent(builder::inventory);

@ -96,9 +96,11 @@ public class DataSnapshot {
return new Builder(plugin); return new Builder(plugin);
} }
// Deserialize a DataSnapshot downloaded from the database (with an ID & Timestamp from the database)
@NotNull @NotNull
@ApiStatus.Internal @ApiStatus.Internal
public static DataSnapshot.Packed deserialize(@NotNull HuskSync plugin, byte[] data) throws IllegalStateException { public static DataSnapshot.Packed deserialize(@NotNull HuskSync plugin, byte[] data, @Nullable UUID id,
@Nullable OffsetDateTime timestamp) throws IllegalStateException {
final DataSnapshot.Packed snapshot = plugin.getDataAdapter().fromBytes(data, DataSnapshot.Packed.class); final DataSnapshot.Packed snapshot = plugin.getDataAdapter().fromBytes(data, DataSnapshot.Packed.class);
if (snapshot.getMinecraftVersion().compareTo(plugin.getMinecraftVersion()) > 0) { if (snapshot.getMinecraftVersion().compareTo(plugin.getMinecraftVersion()) > 0) {
throw new IllegalStateException(String.format("Cannot set data for user because the Minecraft version of " + throw new IllegalStateException(String.format("Cannot set data for user because the Minecraft version of " +
@ -114,7 +116,11 @@ public class DataSnapshot {
} }
if (snapshot.getFormatVersion() < CURRENT_FORMAT_VERSION) { if (snapshot.getFormatVersion() < CURRENT_FORMAT_VERSION) {
if (plugin.getLegacyConverter().isPresent()) { if (plugin.getLegacyConverter().isPresent()) {
return plugin.getLegacyConverter().get().convert(data); return plugin.getLegacyConverter().get().convert(
data,
Objects.requireNonNull(id, "Attempted legacy conversion with null UUID!"),
Objects.requireNonNull(timestamp, "Attempted legacy conversion with null timestamp!")
);
} }
throw new IllegalStateException(String.format( throw new IllegalStateException(String.format(
"No legacy converter to convert format version: %s", snapshot.getFormatVersion() "No legacy converter to convert format version: %s", snapshot.getFormatVersion()
@ -129,6 +135,13 @@ public class DataSnapshot {
return snapshot; return snapshot;
} }
// Deserialize a DataSnapshot from a network message payload (without an ID)
@NotNull
@ApiStatus.Internal
public static DataSnapshot.Packed deserialize(@NotNull HuskSync plugin, byte[] data) throws IllegalStateException {
return deserialize(plugin, data, null, null);
}
/** /**
* Return the ID of the snapshot * Return the ID of the snapshot
* *
@ -393,6 +406,7 @@ public class DataSnapshot {
public static class Builder { public static class Builder {
private final HuskSync plugin; private final HuskSync plugin;
private UUID id;
private SaveCause saveCause; private SaveCause saveCause;
private boolean pinned; private boolean pinned;
private OffsetDateTime timestamp; private OffsetDateTime timestamp;
@ -403,6 +417,19 @@ public class DataSnapshot {
this.pinned = false; this.pinned = false;
this.data = new HashMap<>(); this.data = new HashMap<>();
this.timestamp = OffsetDateTime.now(); this.timestamp = OffsetDateTime.now();
this.id = UUID.randomUUID();
}
/**
* Set the {@link UUID unique ID} of the snapshot
*
* @param id The {@link UUID} of the snapshot
* @return The builder
*/
@NotNull
public Builder id(@NotNull UUID id) {
this.id = id;
return this;
} }
/** /**
@ -661,7 +688,7 @@ public class DataSnapshot {
throw new IllegalStateException("Cannot build DataSnapshot without a save cause"); throw new IllegalStateException("Cannot build DataSnapshot without a save cause");
} }
return new Unpacked( return new Unpacked(
UUID.randomUUID(), id,
pinned || plugin.getSettings().doAutoPin(saveCause), pinned || plugin.getSettings().doAutoPin(saveCause),
timestamp, timestamp,
saveCause, saveCause,

@ -217,7 +217,7 @@ public class MySqlDatabase extends Database {
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) { public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
try (Connection connection = getConnection()) { try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
SELECT `version_uuid`, `timestamp`, `save_cause`, `pinned`, `data` SELECT `version_uuid`, `timestamp`, `data`
FROM `%user_data_table%` FROM `%user_data_table%`
WHERE `player_uuid`=? WHERE `player_uuid`=?
ORDER BY `timestamp` DESC ORDER BY `timestamp` DESC
@ -225,10 +225,14 @@ public class MySqlDatabase extends Database {
statement.setString(1, user.getUuid().toString()); statement.setString(1, user.getUuid().toString());
final ResultSet resultSet = statement.executeQuery(); final ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) { if (resultSet.next()) {
final UUID versionUuid = UUID.fromString(resultSet.getString("version_uuid"));
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(
resultSet.getTimestamp("timestamp").toInstant(), TimeZone.getDefault().toZoneId()
);
final Blob blob = resultSet.getBlob("data"); final Blob blob = resultSet.getBlob("data");
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length()); final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
blob.free(); blob.free();
return Optional.of(DataSnapshot.deserialize(plugin, dataByteArray)); return Optional.of(DataSnapshot.deserialize(plugin, dataByteArray, versionUuid, timestamp));
} }
} }
} catch (SQLException | DataAdapter.AdaptionException e) { } catch (SQLException | DataAdapter.AdaptionException e) {
@ -244,17 +248,21 @@ public class MySqlDatabase extends Database {
final List<DataSnapshot.Packed> retrievedData = new ArrayList<>(); final List<DataSnapshot.Packed> retrievedData = new ArrayList<>();
try (Connection connection = getConnection()) { try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
SELECT `version_uuid`, `timestamp`, `save_cause`, `pinned`, `data` SELECT `version_uuid`, `timestamp`, `data`
FROM `%user_data_table%` FROM `%user_data_table%`
WHERE `player_uuid`=? WHERE `player_uuid`=?
ORDER BY `timestamp` DESC;"""))) { ORDER BY `timestamp` DESC;"""))) {
statement.setString(1, user.getUuid().toString()); statement.setString(1, user.getUuid().toString());
final ResultSet resultSet = statement.executeQuery(); final ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) { while (resultSet.next()) {
final UUID versionUuid = UUID.fromString(resultSet.getString("version_uuid"));
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(
resultSet.getTimestamp("timestamp").toInstant(), TimeZone.getDefault().toZoneId()
);
final Blob blob = resultSet.getBlob("data"); final Blob blob = resultSet.getBlob("data");
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length()); final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
blob.free(); blob.free();
retrievedData.add(DataSnapshot.deserialize(plugin, dataByteArray)); retrievedData.add(DataSnapshot.deserialize(plugin, dataByteArray, versionUuid, timestamp));
} }
return retrievedData; return retrievedData;
} }
@ -269,7 +277,7 @@ public class MySqlDatabase extends Database {
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) { public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
try (Connection connection = getConnection()) { try (Connection connection = getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
SELECT `version_uuid`, `timestamp`, `save_cause`, `pinned`, `data` SELECT `version_uuid`, `timestamp`, `data`
FROM `%user_data_table%` FROM `%user_data_table%`
WHERE `player_uuid`=? AND `version_uuid`=? WHERE `player_uuid`=? AND `version_uuid`=?
ORDER BY `timestamp` DESC ORDER BY `timestamp` DESC
@ -279,9 +287,12 @@ public class MySqlDatabase extends Database {
final ResultSet resultSet = statement.executeQuery(); final ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) { if (resultSet.next()) {
final Blob blob = resultSet.getBlob("data"); final Blob blob = resultSet.getBlob("data");
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(
resultSet.getTimestamp("timestamp").toInstant(), TimeZone.getDefault().toZoneId()
);
final byte[] dataByteArray = blob.getBytes(1, (int) blob.length()); final byte[] dataByteArray = blob.getBytes(1, (int) blob.length());
blob.free(); blob.free();
return Optional.of(DataSnapshot.deserialize(plugin, dataByteArray)); return Optional.of(DataSnapshot.deserialize(plugin, dataByteArray, versionUuid, timestamp));
} }
} }
} catch (SQLException | DataAdapter.AdaptionException e) { } catch (SQLException | DataAdapter.AdaptionException e) {

@ -24,6 +24,9 @@ import net.william278.husksync.adapter.DataAdapter;
import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.data.DataSnapshot;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.time.OffsetDateTime;
import java.util.UUID;
public abstract class LegacyConverter { public abstract class LegacyConverter {
protected final HuskSync plugin; protected final HuskSync plugin;
@ -33,6 +36,7 @@ public abstract class LegacyConverter {
} }
@NotNull @NotNull
public abstract DataSnapshot.Packed convert(@NotNull byte[] data) throws DataAdapter.AdaptionException; public abstract DataSnapshot.Packed convert(@NotNull byte[] data, @NotNull UUID id,
@NotNull OffsetDateTime timestamp) throws DataAdapter.AdaptionException;
} }

@ -3,7 +3,7 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.daemon=true org.gradle.daemon=true
javaVersion=16 javaVersion=16
plugin_version=3.0 plugin_version=3.0.1
plugin_archive=husksync plugin_archive=husksync
plugin_description=A modern, cross-server player data synchronization system plugin_description=A modern, cross-server player data synchronization system

Loading…
Cancel
Save