diff --git a/api/src/main/java/net/luckperms/api/event/log/LogReceiveEvent.java b/api/src/main/java/net/luckperms/api/event/log/LogReceiveEvent.java index 31308d69e..96ac1d9ea 100644 --- a/api/src/main/java/net/luckperms/api/event/log/LogReceiveEvent.java +++ b/api/src/main/java/net/luckperms/api/event/log/LogReceiveEvent.java @@ -33,7 +33,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; import java.util.UUID; /** - * Called when a log entry is received via the MessagingService + * Called when a log entry is received via the MessagingService. + * + *

Note: listening to this event is the same as listening to the {@link LogBroadcastEvent} + * and filtering for {@link LogBroadcastEvent#getOrigin() origin} = + * {@link net.luckperms.api.event.log.LogBroadcastEvent.Origin#REMOTE REMOTE}.

*/ public interface LogReceiveEvent extends LuckPermsEvent { diff --git a/api/src/main/java/net/luckperms/api/event/sync/PostNetworkSyncEvent.java b/api/src/main/java/net/luckperms/api/event/sync/PostNetworkSyncEvent.java new file mode 100644 index 000000000..34dc3f66a --- /dev/null +++ b/api/src/main/java/net/luckperms/api/event/sync/PostNetworkSyncEvent.java @@ -0,0 +1,82 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.luckperms.api.event.sync; + +import net.luckperms.api.event.LuckPermsEvent; +import net.luckperms.api.event.type.Cancellable; +import net.luckperms.api.event.util.Param; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.UUID; + +/** + * Called after a network synchronisation task has been completed. + * + *

Note: the generic {@link PostSyncEvent} will also be called for {@link SyncType#FULL full syncs}.

+ * + * @since 5.5 + */ +public interface PostNetworkSyncEvent extends LuckPermsEvent { + + /** + * Gets the ID of the sync request + * + * @return the id of the sync request + */ + @Param(0) + @NonNull UUID getSyncId(); + + /** + * Gets the sync type. + * + * @return the sync type + */ + @Param(1) + @NonNull SyncType getType(); + + /** + * Gets if a sync occurred. + * + *

For {@link SyncType} = {@link SyncType#FULL FULL}, this method always returns true.

+ * + *

For {@link SyncType} = {@link SyncType#SPECIFIC_USER SPECIFIC_USER}, this method returns true if the + * user in question was online/loaded in memory at the time, and false otherwise.

+ * + * @return if a sync occurred + */ + @Param(2) + boolean didSyncOccur(); + + /** + * Gets the unique id of the specific user that has been synced, if applicable. + * + * @return the unique id of the specific user + */ + @Param(3) + @Nullable UUID getSpecificUserUniqueId(); + +} diff --git a/api/src/main/java/net/luckperms/api/event/sync/PostSyncEvent.java b/api/src/main/java/net/luckperms/api/event/sync/PostSyncEvent.java index 0a048ca04..281b6429f 100644 --- a/api/src/main/java/net/luckperms/api/event/sync/PostSyncEvent.java +++ b/api/src/main/java/net/luckperms/api/event/sync/PostSyncEvent.java @@ -28,7 +28,10 @@ package net.luckperms.api.event.sync; import net.luckperms.api.event.LuckPermsEvent; /** - * Called when an sync task has been completed + * Called after a full synchronisation task has been completed. + * + *

Note: this event is also called after synchronisations that were triggered over the network. + * In other words, this event will be called in addition to {@link PostNetworkSyncEvent}.

*/ public interface PostSyncEvent extends LuckPermsEvent { diff --git a/api/src/main/java/net/luckperms/api/event/sync/PreNetworkSyncEvent.java b/api/src/main/java/net/luckperms/api/event/sync/PreNetworkSyncEvent.java index bf87cbe2a..013d47f39 100644 --- a/api/src/main/java/net/luckperms/api/event/sync/PreNetworkSyncEvent.java +++ b/api/src/main/java/net/luckperms/api/event/sync/PreNetworkSyncEvent.java @@ -29,11 +29,15 @@ import net.luckperms.api.event.LuckPermsEvent; import net.luckperms.api.event.type.Cancellable; import net.luckperms.api.event.util.Param; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import java.util.UUID; /** - * Called before a received network sync task runs + * Called after a request for synchronisation has been received via the messaging service, + * but before it has actually been completed. + * + *

Note: the generic {@link PreSyncEvent} will also be called for {@link SyncType#FULL full syncs}.

*/ public interface PreNetworkSyncEvent extends LuckPermsEvent, Cancellable { @@ -45,4 +49,22 @@ public interface PreNetworkSyncEvent extends LuckPermsEvent, Cancellable { @Param(0) @NonNull UUID getSyncId(); + /** + * Gets the sync type. + * + * @return the sync type + * @since 5.5 + */ + @Param(1) + @NonNull SyncType getType(); + + /** + * Gets the unique id of the specific user that will be synced, if applicable. + * + * @return the unique id of the specific user + * @since 5.5 + */ + @Param(2) + @Nullable UUID getSpecificUserUniqueId(); + } diff --git a/api/src/main/java/net/luckperms/api/event/sync/PreSyncEvent.java b/api/src/main/java/net/luckperms/api/event/sync/PreSyncEvent.java index 41eb5caf9..3961fe443 100644 --- a/api/src/main/java/net/luckperms/api/event/sync/PreSyncEvent.java +++ b/api/src/main/java/net/luckperms/api/event/sync/PreSyncEvent.java @@ -29,7 +29,10 @@ import net.luckperms.api.event.LuckPermsEvent; import net.luckperms.api.event.type.Cancellable; /** - * Called before a sync task runs + * Called just before a full synchronisation task runs. + * + *

Note: this event is also called before synchronisations that were triggered over the network. + * In other words, this event will be called in addition to {@link PreNetworkSyncEvent}.

*/ public interface PreSyncEvent extends LuckPermsEvent, Cancellable { diff --git a/api/src/main/java/net/luckperms/api/event/sync/SyncType.java b/api/src/main/java/net/luckperms/api/event/sync/SyncType.java new file mode 100644 index 000000000..14f7ef0c1 --- /dev/null +++ b/api/src/main/java/net/luckperms/api/event/sync/SyncType.java @@ -0,0 +1,45 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.luckperms.api.event.sync; + +/** + * Represents the type of synchronisation task. + * + * @since 5.5 + */ +public enum SyncType { + + /** + * A full sync will be performed - all groups, users and tracks + */ + FULL, + + /** + * Only a specific user will be synced + */ + SPECIFIC_USER + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java index fe1155135..456ceefb4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java +++ b/common/src/main/java/me/lucko/luckperms/common/event/EventDispatcher.java @@ -70,9 +70,11 @@ import net.luckperms.api.event.player.lookup.UsernameLookupEvent; import net.luckperms.api.event.player.lookup.UsernameValidityCheckEvent; import net.luckperms.api.event.source.Source; import net.luckperms.api.event.sync.ConfigReloadEvent; +import net.luckperms.api.event.sync.PostNetworkSyncEvent; import net.luckperms.api.event.sync.PostSyncEvent; import net.luckperms.api.event.sync.PreNetworkSyncEvent; import net.luckperms.api.event.sync.PreSyncEvent; +import net.luckperms.api.event.sync.SyncType; import net.luckperms.api.event.track.TrackCreateEvent; import net.luckperms.api.event.track.TrackDeleteEvent; import net.luckperms.api.event.track.TrackLoadAllEvent; @@ -270,12 +272,16 @@ public final class EventDispatcher { postAsync(ConfigReloadEvent.class); } + public void dispatchNetworkPostSync(UUID id, SyncType type, boolean didOccur, UUID specificUserUniqueId) { + postAsync(PostNetworkSyncEvent.class, id, type, didOccur, specificUserUniqueId); + } + public void dispatchPostSync() { postAsync(PostSyncEvent.class); } - public boolean dispatchNetworkPreSync(boolean initialState, UUID id) { - return postCancellable(PreNetworkSyncEvent.class, initialState, id); + public boolean dispatchNetworkPreSync(boolean initialState, UUID id, SyncType type, UUID specificUserUniqueId) { + return postCancellable(PreNetworkSyncEvent.class, initialState, id, type, specificUserUniqueId); } public boolean dispatchPreSync(boolean initialState) { @@ -414,6 +420,7 @@ public final class EventDispatcher { UsernameLookupEvent.class, UsernameValidityCheckEvent.class, ConfigReloadEvent.class, + PostNetworkSyncEvent.class, PostSyncEvent.class, PreNetworkSyncEvent.class, PreSyncEvent.class, diff --git a/common/src/main/java/me/lucko/luckperms/common/messaging/LuckPermsMessagingService.java b/common/src/main/java/me/lucko/luckperms/common/messaging/LuckPermsMessagingService.java index 3297b1a2c..ee9cdb205 100644 --- a/common/src/main/java/me/lucko/luckperms/common/messaging/LuckPermsMessagingService.java +++ b/common/src/main/java/me/lucko/luckperms/common/messaging/LuckPermsMessagingService.java @@ -39,6 +39,7 @@ import me.lucko.luckperms.common.util.ExpiringSet; import me.lucko.luckperms.common.util.gson.GsonProvider; import me.lucko.luckperms.common.util.gson.JObject; import net.luckperms.api.actionlog.Action; +import net.luckperms.api.event.sync.SyncType; import net.luckperms.api.messenger.IncomingMessageConsumer; import net.luckperms.api.messenger.Messenger; import net.luckperms.api.messenger.MessengerProvider; @@ -235,29 +236,35 @@ public class LuckPermsMessagingService implements InternalMessagingService, Inco private void processIncomingMessage(Message message) { if (message instanceof UpdateMessage) { UpdateMessage msg = (UpdateMessage) message; + UUID msgId = msg.getId(); - this.plugin.getLogger().info("[Messaging] Received update ping with id: " + msg.getId()); - - if (this.plugin.getEventDispatcher().dispatchNetworkPreSync(false, msg.getId())) { + if (this.plugin.getEventDispatcher().dispatchNetworkPreSync(false, msgId, SyncType.FULL, null)) { return; } - this.plugin.getSyncTaskBuffer().request(); + this.plugin.getLogger().info("[Messaging] Received update ping with id: " + msgId); + this.plugin.getSyncTaskBuffer().request() + .thenRunAsync(() -> this.plugin.getEventDispatcher().dispatchNetworkPostSync(msgId, SyncType.FULL, true, null)); + } else if (message instanceof UserUpdateMessage) { UserUpdateMessage msg = (UserUpdateMessage) message; + UUID msgId = msg.getId(); + UUID userUniqueId = msg.getUserUniqueId(); - User user = this.plugin.getUserManager().getIfLoaded(msg.getUserUniqueId()); + if (this.plugin.getEventDispatcher().dispatchNetworkPreSync(false, msgId, SyncType.SPECIFIC_USER, userUniqueId)) { + return; + } + + User user = this.plugin.getUserManager().getIfLoaded(userUniqueId); if (user == null) { + this.plugin.getEventDispatcher().dispatchNetworkPostSync(msgId, SyncType.SPECIFIC_USER, false, userUniqueId); return; } - this.plugin.getLogger().info("[Messaging] Received user update ping for '" + user.getPlainDisplayName() + "' with id: " + msg.getId()); - - if (this.plugin.getEventDispatcher().dispatchNetworkPreSync(false, msg.getId())) { - return; - } - - this.plugin.getStorage().loadUser(user.getUniqueId(), null); + this.plugin.getLogger().info("[Messaging] Received user update ping for '" + user.getPlainDisplayName() + "' with id: " + msgId); + this.plugin.getStorage().loadUser(user.getUniqueId(), null) + .thenRunAsync(() -> this.plugin.getEventDispatcher().dispatchNetworkPostSync(msgId, SyncType.SPECIFIC_USER, true, userUniqueId)); + } else if (message instanceof ActionLogMessage) { ActionLogMessage msg = (ActionLogMessage) message; diff --git a/standalone/src/test/java/me/lucko/luckperms/standalone/MessagingIntegrationTest.java b/standalone/src/test/java/me/lucko/luckperms/standalone/MessagingIntegrationTest.java index 2deffa4a6..7cc8c6cae 100644 --- a/standalone/src/test/java/me/lucko/luckperms/standalone/MessagingIntegrationTest.java +++ b/standalone/src/test/java/me/lucko/luckperms/standalone/MessagingIntegrationTest.java @@ -26,13 +26,17 @@ package me.lucko.luckperms.standalone; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import me.lucko.luckperms.common.actionlog.LoggedAction; import me.lucko.luckperms.common.messaging.InternalMessagingService; +import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.standalone.utils.TestPluginProvider; import net.luckperms.api.actionlog.Action; import net.luckperms.api.event.EventBus; import net.luckperms.api.event.log.LogReceiveEvent; import net.luckperms.api.event.sync.PreNetworkSyncEvent; +import net.luckperms.api.event.sync.SyncType; +import net.luckperms.api.model.PlayerSaveResult; import net.luckperms.api.platform.Health; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; @@ -49,7 +53,9 @@ import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @Testcontainers @@ -83,25 +89,44 @@ public class MessagingIntegrationTest { .description("hello 123 hello 123") .build(); - // register 2 listeners on plugin B - CountDownLatch latch = new CountDownLatch(2); + UUID exampleUniqueId = UUID.fromString("c1d60c50-70b5-4722-8057-87767557e50d"); + String exampleUsername = "Luck"; + User user = pluginA.plugin().getStorage().loadUser(exampleUniqueId, exampleUsername).join(); + EventBus eventBus = pluginB.app().getApi().getEventBus(); + + CountDownLatch latch1 = new CountDownLatch(1); eventBus.subscribe(PreNetworkSyncEvent.class, e -> { - latch.countDown(); - e.setCancelled(true); + if (e.getType() == SyncType.FULL) { + latch1.countDown(); + e.setCancelled(true); + } }); + + CountDownLatch latch2 = new CountDownLatch(1); + eventBus.subscribe(PreNetworkSyncEvent.class, e -> { + if (e.getType() == SyncType.SPECIFIC_USER && exampleUniqueId.equals(e.getSpecificUserUniqueId())) { + latch2.countDown(); + e.setCancelled(true); + } + }); + + CountDownLatch latch3 = new CountDownLatch(1); eventBus.subscribe(LogReceiveEvent.class, e -> { if (e.getEntry().equals(exampleLogEntry)) { - latch.countDown(); + latch3.countDown(); } }); // send some messages from plugin A to plugin B messagingServiceA.pushUpdate(); + messagingServiceA.pushUserUpdate(user); messagingServiceA.pushLog(exampleLogEntry); // wait for the messages to be sent/received - assertTrue(latch.await(30, TimeUnit.SECONDS)); + assertTrue(latch1.await(10, TimeUnit.SECONDS)); + assertTrue(latch2.await(10, TimeUnit.SECONDS)); + assertTrue(latch3.await(10, TimeUnit.SECONDS)); } }