From 5be9c84cb1b8cca1545bd35a1ca772b97973edc2 Mon Sep 17 00:00:00 2001 From: Yanbing Zhao Date: Mon, 30 Sep 2024 00:02:31 +0800 Subject: [PATCH] Add revision for vote comments and optimize code --- .../org/teacon/voteme/item/VoterItem.java | 36 ++--- .../org/teacon/voteme/screen/VoterScreen.java | 3 +- .../voteme/sync/AnnouncementSerializer.java | 25 ++-- .../voteme/sync/DetachedSynchronizer.java | 1 + .../teacon/voteme/sync/RedisSynchronizer.java | 62 +++----- .../teacon/voteme/sync/StatsAccumulator.java | 4 +- .../teacon/voteme/sync/VoteSynchronizer.java | 2 +- .../teacon/voteme/vote/VoteDataStorage.java | 134 ++++++------------ 8 files changed, 96 insertions(+), 171 deletions(-) diff --git a/src/main/java/org/teacon/voteme/item/VoterItem.java b/src/main/java/org/teacon/voteme/item/VoterItem.java index 7b13737..c39087d 100644 --- a/src/main/java/org/teacon/voteme/item/VoterItem.java +++ b/src/main/java/org/teacon/voteme/item/VoterItem.java @@ -70,25 +70,25 @@ private VoterItem(Properties properties) { @Override public void appendHoverText(ItemStack stack, Item.TooltipContext context, List tooltip, TooltipFlag tooltipFlag) { - UUID artifactID = stack.get(ArtifactID.INSTANCE); - Optional artifactNames = VoteArtifactNames.effective(); - if (artifactNames.isPresent() && artifactID != null && !artifactNames.get().getName(artifactID).isEmpty()) { - MutableComponent artifactText = artifactNames.get().toText(artifactID).withStyle(ChatFormatting.GREEN); - tooltip.add(Component.translatable("gui.voteme.voter.current_artifact_hint", artifactText).withStyle(ChatFormatting.GRAY)); - if (!VoteCategoryHandler.getIds().isEmpty()) { - tooltip.add(Component.empty()); - } - for (ResourceLocation categoryID : VoteCategoryHandler.getIds()) { - Optional categoryOptional = VoteCategoryHandler.getCategory(categoryID); - if (categoryOptional.isPresent()) { - Component categoryName = categoryOptional.get().name; - MutableComponent categoryText = Component.empty().append(categoryName).withStyle(ChatFormatting.YELLOW); - tooltip.add(Component.translatable("gui.voteme.counter.category_hint", categoryText).withStyle(ChatFormatting.GRAY)); - } + UUID artifactID = stack.get(ArtifactID.INSTANCE); + Optional artifactNames = VoteArtifactNames.effective(); + if (artifactNames.isPresent() && artifactID != null && !artifactNames.get().getName(artifactID).isEmpty()) { + MutableComponent artifactText = artifactNames.get().toText(artifactID).withStyle(ChatFormatting.GREEN); + tooltip.add(Component.translatable("gui.voteme.voter.current_artifact_hint", artifactText).withStyle(ChatFormatting.GRAY)); + if (!VoteCategoryHandler.getIds().isEmpty()) { + tooltip.add(Component.empty()); + } + for (ResourceLocation categoryID : VoteCategoryHandler.getIds()) { + Optional categoryOptional = VoteCategoryHandler.getCategory(categoryID); + if (categoryOptional.isPresent()) { + Component categoryName = categoryOptional.get().name; + MutableComponent categoryText = Component.empty().append(categoryName).withStyle(ChatFormatting.YELLOW); + tooltip.add(Component.translatable("gui.voteme.counter.category_hint", categoryText).withStyle(ChatFormatting.GRAY)); } - } else { - tooltip.add(Component.translatable("gui.voteme.voter.empty_artifact_hint").withStyle(ChatFormatting.GRAY)); } + } else { + tooltip.add(Component.translatable("gui.voteme.voter.empty_artifact_hint").withStyle(ChatFormatting.GRAY)); + } } @Override @@ -136,7 +136,7 @@ public Component getName(ItemStack stack) { public ItemStack copyFrom(int voterSize, ItemStack stack) { ItemStack result = new ItemStack(this, voterSize); - result.copyFrom(stack, ArtifactID.INSTANCE); + result.set(ArtifactID.INSTANCE, stack.get(ArtifactID.INSTANCE)); return result; } } diff --git a/src/main/java/org/teacon/voteme/screen/VoterScreen.java b/src/main/java/org/teacon/voteme/screen/VoterScreen.java index 152cd33..4515f33 100644 --- a/src/main/java/org/teacon/voteme/screen/VoterScreen.java +++ b/src/main/java/org/teacon/voteme/screen/VoterScreen.java @@ -24,7 +24,6 @@ import org.teacon.voteme.network.ShowVoterPacket; import org.teacon.voteme.network.SubmitCommentPacket; import org.teacon.voteme.network.SubmitVotePacket; -import org.teacon.voteme.network.VoteMePacketManager; import javax.annotation.ParametersAreNonnullByDefault; import java.util.*; @@ -294,7 +293,7 @@ public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, doubl } @Override - public void onClick(double mouseX, double mouseY) { + public void onClick(double mouseX, double mouseY, int button) { double dx = mouseX - this.getX(), dy = mouseY - this.getY(); if (dx >= 192 && dy >= this.slideCenter + this.halfSliderHeight) { this.changeSlideCenter(this.slideCenter + 1); diff --git a/src/main/java/org/teacon/voteme/sync/AnnouncementSerializer.java b/src/main/java/org/teacon/voteme/sync/AnnouncementSerializer.java index 94ef2d7..bc6061d 100644 --- a/src/main/java/org/teacon/voteme/sync/AnnouncementSerializer.java +++ b/src/main/java/org/teacon/voteme/sync/AnnouncementSerializer.java @@ -37,30 +37,32 @@ public final class AnnouncementSerializer { private static final String KEY_DISABLED = "Disabled"; private static final String KEY_LEVEL = "Level"; private static final String KEY_LEVEL_COUNTS = "LevelCounts"; + private static final String KEY_REVISION = "Revision"; private static final String KEY_VOTER = "VoterUUID"; private static final String KEY_VOTE_ROLE = "VoteRole"; private static final String KEY_VOTE_ROLES = "VoteRoles"; private static final String KEY_VOTE_TIME = "VoteTime"; public static Optional serialize(Announcement announcement) { - try { - CompoundTag nbt = new CompoundTag(); - if (announcement instanceof Artifact artifact) { + CompoundTag nbt = new CompoundTag(); + switch (announcement) { + case Artifact artifact -> { nbt.putString(KEY_ANNOUNCEMENT, ARTIFACT); nbt.putUUID(KEY_ARTIFACT, artifact.key().artifactID()); nbt.putString(KEY_ARTIFACT_NAME, artifact.name()); artifact.alias().ifPresent(alias -> nbt.putString(KEY_ALIAS, alias)); return Optional.of(nbt); } - if (announcement instanceof Comments comments) { + case Comments comments -> { nbt.putString(KEY_ANNOUNCEMENT, COMMENTS); nbt.putUUID(KEY_ARTIFACT, comments.key().artifactID()); nbt.putUUID(KEY_VOTER, comments.key().voterID()); + nbt.putInt(KEY_REVISION, comments.key().revision()); nbt.put(KEY_COMMENTS, Util.make(new ListTag(), tag -> comments .comments().forEach(c -> tag.add(StringTag.valueOf(c))))); return Optional.of(nbt); } - if (announcement instanceof Vote vote) { + case Vote vote -> { nbt.putString(KEY_ANNOUNCEMENT, VOTE); nbt.putUUID(KEY_ARTIFACT, vote.key().artifactID()); nbt.putString(KEY_CATEGORY, vote.key().categoryID().toString()); @@ -71,26 +73,21 @@ public static Optional serialize(Announcement announcement) { nbt.putLong(KEY_VOTE_TIME, vote.time().toEpochMilli()); return Optional.of(nbt); } - if (announcement instanceof VoteDisabled voteDisabled) { + case VoteDisabled voteDisabled -> { nbt.putString(KEY_ANNOUNCEMENT, VOTE_DISABLED); nbt.putUUID(KEY_ARTIFACT, voteDisabled.key().artifactID()); nbt.putString(KEY_CATEGORY, voteDisabled.key().categoryID().toString()); voteDisabled.disabled().ifPresent(disabled -> nbt.putBoolean(KEY_DISABLED, disabled)); return Optional.of(nbt); } - if (announcement instanceof VoteStats voteStats) { + case VoteStats voteStats -> { nbt.putString(KEY_ANNOUNCEMENT, VOTE_STATS); nbt.putUUID(KEY_ARTIFACT, voteStats.key().artifactID()); nbt.putString(KEY_CATEGORY, voteStats.key().categoryID().toString()); nbt.putString(KEY_VOTE_ROLE, voteStats.key().roleID().toString()); - // noinspection UnstableApiUsage nbt.putIntArray(KEY_LEVEL_COUNTS, voteStats.counts().toArray()); return Optional.of(nbt); } - throw new IllegalArgumentException("unsupported announcement type: " + announcement.getClass()); - } catch (IllegalArgumentException e) { - VoteMe.LOGGER.warn("Failed to serialize " + announcement + " to nbt", e); - return Optional.empty(); } } @@ -104,7 +101,8 @@ yield new Artifact(key, nbt.getString(KEY_ARTIFACT_NAME), nbt.contains(KEY_ALIAS Tag.TAG_STRING) ? Optional.of(nbt.getString(KEY_ALIAS)) : Optional.empty()); } case COMMENTS -> { - CommentsKey key = new CommentsKey(nbt.getUUID(KEY_ARTIFACT), nbt.getUUID(KEY_VOTER)); + CommentsKey key = new CommentsKey( + nbt.getUUID(KEY_ARTIFACT), nbt.getUUID(KEY_VOTER), nbt.getInt(KEY_REVISION)); yield new Comments(key, nbt.getList(KEY_COMMENTS, Tag.TAG_STRING) .stream().map(Tag::getAsString).collect(toImmutableList())); } @@ -125,7 +123,6 @@ yield new VoteDisabled(key, nbt.contains(KEY_DISABLED, case VOTE_STATS -> { VoteStatsKey key = new VoteStatsKey(nbt.getUUID(KEY_ARTIFACT), ResourceLocation.parse(nbt .getString(KEY_CATEGORY)), ResourceLocation.parse(nbt.getString(KEY_VOTE_ROLE))); - // noinspection UnstableApiUsage yield new VoteStats(key, copyOf(nbt.getIntArray(KEY_LEVEL_COUNTS))); } default -> throw new IllegalArgumentException("unsupported announce key: " + announceKey); diff --git a/src/main/java/org/teacon/voteme/sync/DetachedSynchronizer.java b/src/main/java/org/teacon/voteme/sync/DetachedSynchronizer.java index 78f63e4..f4dfec8 100644 --- a/src/main/java/org/teacon/voteme/sync/DetachedSynchronizer.java +++ b/src/main/java/org/teacon/voteme/sync/DetachedSynchronizer.java @@ -61,6 +61,7 @@ public void publish(Collection announcements) { @Override public Collection dequeue() { Preconditions.checkArgument(this.server.isSameThread(), "server thread"); + // noinspection SizeReplaceableByIsEmpty if (this.queued.size() > 0) { VoteMe.LOGGER.info("Retrieving {} announcement locally.", this.queued.size()); Collection result = this.queued; diff --git a/src/main/java/org/teacon/voteme/sync/RedisSynchronizer.java b/src/main/java/org/teacon/voteme/sync/RedisSynchronizer.java index e6c569f..81c4d29 100644 --- a/src/main/java/org/teacon/voteme/sync/RedisSynchronizer.java +++ b/src/main/java/org/teacon/voteme/sync/RedisSynchronizer.java @@ -32,7 +32,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.concurrent.CompletableFuture.allOf; -import static java.util.concurrent.CompletableFuture.failedStage; import static org.teacon.voteme.sync.AnnouncementSerializer.*; @MethodsReturnNonnullByDefault @@ -76,11 +75,11 @@ private void tickVoteUpdates() { } List watchedVotes = new ArrayList<>(); accumulator.buildAffectedVotes(watchedVotes); + // noinspection SizeReplaceableByIsEmpty if (watchedVotes.size() > 0) { String[] watchedKeys = watchedVotes.stream().map(v -> toRedisKey(v.key())).toArray(String[]::new); VoteMe.LOGGER.info("Watch {} key(s) for updating vote related data in redis.", watchedKeys.length); List affectedVotes = new ArrayList<>(); - // noinspection UnstableApiUsage Map affectedStatsMap = new HashMap<>(); RedisAsyncCommands async = this.connection.async(); AtomicBoolean succeed = new AtomicBoolean(); @@ -117,15 +116,11 @@ private void tickVoteUpdates() { futures.add(async.hset(key, builder.build()).toCompletableFuture()); } // submit affected stats - // noinspection UnstableApiUsage for (Map.Entry entry : affectedStatsMap.entrySet()) { String key = toRedisKey(entry.getKey()); - // noinspection UnstableApiUsage ImmutableIntArray counts = entry.getValue(); - // noinspection UnstableApiUsage int bound = counts.length(); for (int i = 0; i < bound; ++i) { - // noinspection UnstableApiUsage futures.add(async.hincrby(key, "level:" + i, counts.get(i)).toCompletableFuture()); } } @@ -138,7 +133,6 @@ private void tickVoteUpdates() { for (Vote affectedVote : affectedVotes) { async.publish(SYNC, serialize(affectedVote).orElseThrow().toString()); } - // noinspection UnstableApiUsage for (Map.Entry entry : affectedStatsMap.entrySet()) { VoteStats voteStats = new VoteStats(entry.getKey(), entry.getValue()); async.publish(SYNC, serialize(voteStats).orElseThrow().toString()); @@ -154,22 +148,13 @@ private void tickVoteUpdates() { } private static String toRedisKey(AnnounceKey announceKey) { - if (announceKey instanceof ArtifactKey key) { - return ARTIFACT + ":" + key.artifactID(); - } - if (announceKey instanceof CommentsKey key) { - return COMMENTS + ":" + key.artifactID() + ":" + key.voterID(); - } - if (announceKey instanceof VoteKey key) { - return VOTE + ":" + key.artifactID() + ":" + key.categoryID() + ":" + key.voterID(); - } - if (announceKey instanceof VoteDisabledKey key) { - return VOTE_DISABLED + ":" + key.artifactID() + ":" + key.categoryID(); - } - if (announceKey instanceof VoteStatsKey key) { - return VOTE_STATS + ":" + key.artifactID() + ":" + key.categoryID() + ":" + key.roleID(); - } - throw new IllegalArgumentException("unsupported announce key"); + return switch (announceKey) { + case ArtifactKey key -> ARTIFACT + ":" + key.artifactID(); + case CommentsKey key -> COMMENTS + ":" + key.artifactID() + ":" + key.voterID() + ":"; + case VoteKey key -> VOTE + ":" + key.artifactID() + ":" + key.categoryID() + ":" + key.voterID(); + case VoteDisabledKey key -> VOTE_DISABLED + ":" + key.artifactID() + ":" + key.categoryID(); + case VoteStatsKey key -> VOTE_STATS + ":" + key.artifactID() + ":" + key.categoryID() + ":" + key.roleID(); + }; } private static AnnounceKey fromRedisKey(String redisKey) { @@ -183,10 +168,11 @@ private static AnnounceKey fromRedisKey(String redisKey) { } case COMMENTS -> { String[] parts = redisKeyParts[2].split(":"); - checkArgument(parts.length == 2, "invalid message"); + checkArgument(parts.length == 2 || parts.length == 3, "invalid message"); UUID artifactID = UUID.fromString(parts[0]); UUID voterID = UUID.fromString(parts[1]); - return new CommentsKey(artifactID, voterID); + int revision = parts.length > 2 ? Integer.parseInt(parts[2]) : 0; + return new CommentsKey(artifactID, voterID, revision); } case VOTE -> { String[] parts = redisKeyParts[2].split(":"); @@ -217,9 +203,8 @@ private static AnnounceKey fromRedisKey(String redisKey) { private static CompletableFuture dispatch(AnnounceKey announceKey, RedisAsyncCommands async) { - CompletionStage stage = failedStage(new IllegalArgumentException("unsupported announce key")); - if (announceKey instanceof ArtifactKey key) { - stage = async.hgetall(toRedisKey(key)).thenApplyAsync(map -> { + CompletionStage stage = switch (announceKey) { + case ArtifactKey key -> async.hgetall(toRedisKey(key)).thenApplyAsync(map -> { String name = map.getOrDefault("name", ""); Optional optional = Optional.empty(); if (map.containsKey("alias")) { @@ -230,19 +215,14 @@ private static CompletableFuture dispatch(AnnounceKe } return announceKey.cast(new Artifact(key, name, optional)); }); - } - if (announceKey instanceof CommentsKey key) { - stage = async.lrange(toRedisKey(key), 0, Integer.MAX_VALUE).thenApplyAsync(list -> { + case CommentsKey key -> async.lrange(toRedisKey(key), 0, Integer.MAX_VALUE).thenApplyAsync(list -> { ImmutableList comments = ImmutableList.copyOf(list).reverse(); return announceKey.cast(new Comments(key, comments)); }); - } - if (announceKey instanceof VoteKey key) { - stage = async.hgetall(toRedisKey(key)).thenApplyAsync(map -> { + case VoteKey key -> async.hgetall(toRedisKey(key)).thenApplyAsync(map -> { int level = Integer.parseInt(map.getOrDefault("level", "0")); checkArgument(level >= 0 && level <= 5, "level out of range from 1 to 5"); int expectedSize = Math.max(0, map.size() - 2); - // noinspection UnstableApiUsage ImmutableSet.Builder roles = ImmutableSet.builderWithExpectedSize(expectedSize); for (int i = 0; map.containsKey("role:" + i); ++i) { ResourceLocation role = ResourceLocation.parse(map.get("role:" + i)); @@ -251,9 +231,7 @@ private static CompletableFuture dispatch(AnnounceKe Instant time = Instant.parse(checkNotNull(map.getOrDefault("time", Instant.EPOCH.toString()))); return announceKey.cast(new Vote(key, level, roles.build(), time)); }); - } - if (announceKey instanceof VoteDisabledKey key) { - stage = async.get(toRedisKey(key)).thenApplyAsync(string -> { + case VoteDisabledKey key -> async.get(toRedisKey(key)).thenApplyAsync(string -> { Optional disabled = switch (String.valueOf(string)) { case "null" -> Optional.empty(); case "true" -> Optional.of(Boolean.TRUE); @@ -262,20 +240,17 @@ private static CompletableFuture dispatch(AnnounceKe }; return announceKey.cast(new VoteDisabled(key, disabled)); }); - } - if (announceKey instanceof VoteStatsKey key) { - stage = async.hgetall(toRedisKey(key)).thenApplyAsync(map -> { + case VoteStatsKey key -> async.hgetall(toRedisKey(key)).thenApplyAsync(map -> { int count0 = Integer.parseInt(map.getOrDefault("level:0", "0")); int count1 = Integer.parseInt(map.getOrDefault("level:1", "0")); int count2 = Integer.parseInt(map.getOrDefault("level:2", "0")); int count3 = Integer.parseInt(map.getOrDefault("level:3", "0")); int count4 = Integer.parseInt(map.getOrDefault("level:4", "0")); int count5 = Integer.parseInt(map.getOrDefault("level:5", "0")); - // noinspection UnstableApiUsage ImmutableIntArray counts = ImmutableIntArray.of(count0, count1, count2, count3, count4, count5); return announceKey.cast(new VoteStats(key, counts)); }); - } + }; return stage.toCompletableFuture(); } @@ -358,6 +333,7 @@ public Collection dequeue() { this.scan(ScanCursor.of("0"), new ScanArgs().match(VOTE_DISABLED + ":*").limit(BATCH_MAXIMUM)); this.scan(ScanCursor.of("0"), new ScanArgs().match(VOTE_STATS + ":*").limit(BATCH_MAXIMUM)); } + // noinspection SizeReplaceableByIsEmpty if (this.receivedAnnouncements.size() > 0) { VoteMe.LOGGER.info("Retrieving {} announcement(s) from redis.", this.receivedAnnouncements.size()); Collection result = this.receivedAnnouncements; diff --git a/src/main/java/org/teacon/voteme/sync/StatsAccumulator.java b/src/main/java/org/teacon/voteme/sync/StatsAccumulator.java index 1b2c073..d5b88e3 100644 --- a/src/main/java/org/teacon/voteme/sync/StatsAccumulator.java +++ b/src/main/java/org/teacon/voteme/sync/StatsAccumulator.java @@ -29,7 +29,7 @@ private void increase(@Nullable Vote vote) { for (ResourceLocation roleID : vote.roles()) { VoteStatsKey key = new VoteStatsKey(vote.key().artifactID(), vote.key().categoryID(), roleID); this.countMaps.get(vote.level()).mergeInt(key, 1, Integer::sum); - this.countMaps.get(0).mergeInt(key, -1, Integer::sum); + this.countMaps.getFirst().mergeInt(key, -1, Integer::sum); } } } @@ -39,7 +39,7 @@ private void decrease(@Nullable Vote vote) { for (ResourceLocation roleID : vote.roles()) { VoteStatsKey key = new VoteStatsKey(vote.key().artifactID(), vote.key().categoryID(), roleID); this.countMaps.get(vote.level()).mergeInt(key, -1, Integer::sum); - this.countMaps.get(0).mergeInt(key, 1, Integer::sum); + this.countMaps.getFirst().mergeInt(key, 1, Integer::sum); } } } diff --git a/src/main/java/org/teacon/voteme/sync/VoteSynchronizer.java b/src/main/java/org/teacon/voteme/sync/VoteSynchronizer.java index b053e57..0a2dfb4 100644 --- a/src/main/java/org/teacon/voteme/sync/VoteSynchronizer.java +++ b/src/main/java/org/teacon/voteme/sync/VoteSynchronizer.java @@ -50,7 +50,7 @@ record Artifact(ArtifactKey key, String name, Optional alias) implements @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault - record CommentsKey(UUID artifactID, UUID voterID) implements AnnounceKey { + record CommentsKey(UUID artifactID, UUID voterID, int revision) implements AnnounceKey { @Override public Comments cast(Announcement announcement) { return (Comments) announcement; diff --git a/src/main/java/org/teacon/voteme/vote/VoteDataStorage.java b/src/main/java/org/teacon/voteme/vote/VoteDataStorage.java index a820010..02bff84 100644 --- a/src/main/java/org/teacon/voteme/vote/VoteDataStorage.java +++ b/src/main/java/org/teacon/voteme/vote/VoteDataStorage.java @@ -1,9 +1,6 @@ package org.teacon.voteme.vote; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Table; -import com.google.common.collect.TreeBasedTable; +import com.google.common.collect.*; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -50,7 +47,7 @@ public final class VoteDataStorage extends SavedData implements Closeable { private final VoteArtifactNames artifactNames; private final Int2ObjectMap voteLists; private final Table voteListIDs; - private final Table> voteComments; + private final Table voteComments; private final VoteSynchronizer sync; @@ -80,11 +77,9 @@ public VoteDataStorage(CompoundTag nbt, HolderLookup.Provider holderLookupProvid @SuppressWarnings("deprecation") private VoteSynchronizer loadSynchronizer() { + MinecraftServer server = Objects.requireNonNull(ServerLifecycleHooks.getCurrentServer()); String uri = StrSubstitutor.replace(VoteMe.CONFIG.REDIS_ATTACH_URI.get(), System.getenv()); - if (!uri.isBlank()) { - return new RedisSynchronizer(ServerLifecycleHooks.getCurrentServer(), uri.strip()); - } - return new DetachedSynchronizer(ServerLifecycleHooks.getCurrentServer()); + return uri.isBlank() ? new DetachedSynchronizer(server) : new RedisSynchronizer(server, uri.strip()); } private void tick() { @@ -106,38 +101,35 @@ private void tick() { } private void handle(VoteSynchronizer.Announcement announcement) { - if (announcement instanceof VoteSynchronizer.Artifact artifact) { - this.artifactNames.publish(artifact); - return; - } - if (announcement instanceof VoteSynchronizer.Comments comments) { - this.handleCommentsAnnouncement(comments); - return; - } - if (announcement instanceof VoteSynchronizer.VoteDisabled voteDisabled) { - int id = this.getIdOrCreate(voteDisabled.key().artifactID(), voteDisabled.key().categoryID()); - this.getVoteList(id).ifPresent(v -> v.publish(voteDisabled)); - return; - } - if (announcement instanceof VoteSynchronizer.Vote vote) { - int id = this.getIdOrCreate(vote.key().artifactID(), vote.key().categoryID()); - this.getVoteList(id).ifPresent(v -> v.publish(vote)); - return; - } - if (announcement instanceof VoteSynchronizer.VoteStats voteStats) { - int id = this.getIdOrCreate(voteStats.key().artifactID(), voteStats.key().categoryID()); - this.getVoteList(id).ifPresent(v -> v.publish(voteStats)); - return; + switch (announcement) { + case VoteSynchronizer.Artifact artifact -> this.artifactNames.publish(artifact); + case VoteSynchronizer.Comments comments -> this.handleCommentsAnnouncement(comments); + case VoteSynchronizer.VoteDisabled voteDisabled -> { + int id = this.getIdOrCreate(voteDisabled.key().artifactID(), voteDisabled.key().categoryID()); + this.getVoteList(id).ifPresent(v -> v.publish(voteDisabled)); + } + case VoteSynchronizer.Vote vote -> { + int id = this.getIdOrCreate(vote.key().artifactID(), vote.key().categoryID()); + this.getVoteList(id).ifPresent(v -> v.publish(vote)); + } + case VoteSynchronizer.VoteStats voteStats -> { + int id = this.getIdOrCreate(voteStats.key().artifactID(), voteStats.key().categoryID()); + this.getVoteList(id).ifPresent(v -> v.publish(voteStats)); + } } - throw new IllegalArgumentException("unsupported announcement type: " + announcement.getClass()); } private void handleCommentsAnnouncement(VoteSynchronizer.Comments comments) { - this.voteComments.put(comments.key().artifactID(), comments.key().voterID(), comments.comments()); + UUID artifactID = comments.key().artifactID(), voterID = comments.key().voterID(); + CommentsEntry oldEntry = this.voteComments.get(artifactID, voterID); + if (oldEntry == null || oldEntry.revision() <= comments.key().revision()) { + CommentsEntry newEntry = new CommentsEntry(comments.key().revision(), comments.comments()); + this.voteComments.put(artifactID, voterID, newEntry); + } } - private void emitCommentsAnnouncement(UUID artifactID, UUID voterID, ImmutableList comments) { - VoteSynchronizer.CommentsKey key = new VoteSynchronizer.CommentsKey(artifactID, voterID); + private void emitCommentsAnnouncement(UUID artifactID, UUID voterID, int revision, ImmutableList comments) { + VoteSynchronizer.CommentsKey key = new VoteSynchronizer.CommentsKey(artifactID, voterID, revision); this.sync.publish(List.of(new VoteSynchronizer.Comments(key, comments))); this.setDirty(); } @@ -176,27 +168,23 @@ public Optional getVoteList(int id) { public static ImmutableList getCommentFor(VoteDataStorage handler, UUID artifactID, UUID voterID) { if (handler.voteComments.contains(artifactID, voterID)) { - return Objects.requireNonNull(handler.voteComments.get(artifactID, voterID)); + return Objects.requireNonNull(handler.voteComments.get(artifactID, voterID)).comments(); } return ImmutableList.of(); } public static Map> getAllCommentsFor(VoteDataStorage handler, UUID artifactID) { - return Collections.unmodifiableMap(handler.voteComments.row(artifactID)); + Map map = handler.voteComments.row(artifactID); + return Collections.unmodifiableMap(Maps.transformValues(map, CommentsEntry::comments)); } public static void putCommentFor(VoteDataStorage handler, UUID artifactID, UUID voterID, List newComments) { - if (newComments.isEmpty()) { - ImmutableList oldComments = handler.voteComments.remove(artifactID, voterID); - if (!Objects.requireNonNullElse(oldComments, ImmutableList.of()).isEmpty()) { - handler.emitCommentsAnnouncement(artifactID, voterID, ImmutableList.of()); - } - } else { + CommentsEntry old = handler.voteComments.get(artifactID, voterID); + if (old == null ? !newComments.isEmpty() : !newComments.equals(old.comments())) { + int revision = old == null ? 0 : old.revision() + 1; ImmutableList comments = ImmutableList.copyOf(newComments); - ImmutableList oldComments = handler.voteComments.put(artifactID, voterID, comments); - if (!comments.equals(Objects.requireNonNullElse(oldComments, ImmutableList.of()))) { - handler.emitCommentsAnnouncement(artifactID, voterID, comments); - } + handler.voteComments.put(artifactID, voterID, new CommentsEntry(revision, comments)); + handler.emitCommentsAnnouncement(artifactID, voterID, revision, comments); } } @@ -333,48 +321,8 @@ public void load(CompoundTag nbt, HolderLookup.Provider holderLookupProvider) { } } - /* * * * * * * * LEGACY PART START * * * * * * * */ - - // vote lists - ListTag lists = nbt.getList("VoteLists", Tag.TAG_COMPOUND); - for (int i = 0, size = lists.size(); i < size; ++i) { - CompoundTag child = lists.getCompound(i); - VoteSynchronizer.VoteDisabledKey key = VoteList.deserializeKey(child); - int hint = child.contains("VoteListIndex", Tag.TAG_INT) ? child.getInt("VoteListIndex") : this.nextIndex; - int id = this.getIdOrCreate(key.artifactID(), key.categoryID(), hint); - this.voteLists.get(id).loadLegacyNBT(child); - } - - // artifacts - int loadedArtifactSize = this.artifactNames.loadLegacyNBT(nbt); - - // comments - int commentsSize = 0; - CompoundTag commentsCollection = nbt.getCompound("VoteComments"); - for (String artifactID : commentsCollection.getAllKeys()) { - CompoundTag allComments = commentsCollection.getCompound(artifactID); - for (String voterID : allComments.getAllKeys()) { - ImmutableList comments = allComments.getList(voterID, Tag.TAG_STRING) - .stream().map(Tag::getAsString).collect(ImmutableList.toImmutableList()); - if (comments.isEmpty()) { - this.voteComments.put(UUID.fromString(artifactID), UUID.fromString(voterID), comments); - } else { - this.voteComments.remove(UUID.fromString(artifactID), UUID.fromString(voterID)); - } - commentsSize += 1; - this.emitCommentsAnnouncement(UUID.fromString(artifactID), UUID.fromString(voterID), comments); - } - } - - /* * * * * * * * LEGACY PART FINISH * * * * * * * */ - int size = 1 + hintTags.size() + announcementTags.size(); - int legacySize = lists.size() + loadedArtifactSize + commentsSize; - if (legacySize > 0) { - VoteMe.LOGGER.info("Loaded {} data and {} legacy data on server.", size, legacySize); - } else { - VoteMe.LOGGER.info("Loaded {} data on server.", size); - } + VoteMe.LOGGER.info("Loaded {} data on server.", size); } @Override @@ -398,9 +346,9 @@ public CompoundTag save(CompoundTag nbt, HolderLookup.Provider holderLookupProvi ListTag announcementTags = new ListTag(); List announcements = new ArrayList<>(); this.artifactNames.buildAnnouncements(announcements); - for (Table.Cell> e : this.voteComments.cellSet()) { - VoteSynchronizer.CommentsKey key = new VoteSynchronizer.CommentsKey(e.getRowKey(), e.getColumnKey()); - announcements.add(new VoteSynchronizer.Comments(key, e.getValue())); + for (Table.Cell e : this.voteComments.cellSet()) { + VoteSynchronizer.CommentsKey key = new VoteSynchronizer.CommentsKey(e.getRowKey(), e.getColumnKey(), e.getValue().revision()); + announcements.add(new VoteSynchronizer.Comments(key, e.getValue().comments())); } this.voteLists.values().forEach(v -> v.buildAnnouncements(announcements)); announcements.forEach(announcement -> serialize(announcement).ifPresent(announcementTags::add)); @@ -414,4 +362,8 @@ public CompoundTag save(CompoundTag nbt, HolderLookup.Provider holderLookupProvi public void close() throws IOException { this.sync.close(); } + + public record CommentsEntry(int revision, ImmutableList comments) { + // nothing here + } }