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));
}
}