Implement new compressed json import/export format

The old "command list" style is now only supported for import.
This commit is contained in:
Luck 2019-10-14 17:25:37 +01:00
parent 7764a04d46
commit 4b0574710e
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
32 changed files with 783 additions and 676 deletions

View File

@ -29,10 +29,10 @@ import me.lucko.luckperms.bukkit.compat.CraftBukkitUtil;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.sender.SenderFactory;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.adapter.bukkit.TextAdapter;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.Tristate;
import org.bukkit.command.CommandSender;
@ -82,7 +82,7 @@ public class BukkitSenderFactory extends SenderFactory<CommandSender> {
TextAdapter.sendComponent(sender, message);
} else {
// Fallback to legacy format
sendMessage(sender, TextUtils.toLegacy(message));
sendMessage(sender, LegacyComponentSerializer.INSTANCE.serialize(message));
}
}

View File

@ -29,10 +29,10 @@ import me.lucko.luckperms.bungee.event.TristateCheckEvent;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.sender.SenderFactory;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.adapter.bungeecord.TextAdapter;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.Tristate;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@ -62,7 +62,7 @@ public class BungeeSenderFactory extends SenderFactory<CommandSender> {
@Override
protected void sendMessage(CommandSender sender, String s) {
sendMessage(sender, TextUtils.fromLegacy(s));
sendMessage(sender, LegacyComponentSerializer.INSTANCE.deserialize(s));
}
@Override

View File

@ -25,32 +25,36 @@
package me.lucko.luckperms.common.backup;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.model.manager.group.GroupManager;
import me.lucko.luckperms.common.node.factory.NodeCommandFactory;
import me.lucko.luckperms.common.node.utils.NodeJsonSerializer;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.storage.Storage;
import me.lucko.luckperms.common.util.ProgressLogger;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import me.lucko.luckperms.common.util.gson.JArray;
import me.lucko.luckperms.common.util.gson.JObject;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@ -60,9 +64,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
/**
* Handles export operations
@ -70,15 +73,6 @@ import java.util.stream.Collectors;
public class Exporter implements Runnable {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
private static void write(BufferedWriter writer, String s) {
try {
writer.write(s);
writer.newLine();
} catch (IOException e) {
e.printStackTrace();
}
}
private final LuckPermsPlugin plugin;
private final Sender executor;
private final Path filePath;
@ -98,184 +92,128 @@ public class Exporter implements Runnable {
@Override
public void run() {
try (BufferedWriter writer = Files.newBufferedWriter(this.filePath, StandardCharsets.UTF_8)) {
this.log.log("Starting.");
JsonObject file = new JsonObject();
file.add("metadata", new JObject()
.add("generatedBy", this.executor.getNameWithLocation())
.add("generatedAt", DATE_FORMAT.format(new Date(System.currentTimeMillis())))
.toJson());
write(writer, "# LuckPerms Export File");
write(writer, "# Generated by " + this.executor.getNameWithLocation() + " at " + DATE_FORMAT.format(new Date(System.currentTimeMillis())));
write(writer, "");
this.log.log("Gathering group data...");
file.add("groups", exportGroups());
// Export Groups
this.log.log("Starting group export.");
this.log.log("Gathering track data...");
file.add("tracks", exportTracks());
// Create the actual groups first
write(writer, "# Create groups");
if (this.includeUsers) {
this.log.log("Gathering user data...");
file.add("users", exportUsers());
}
AtomicInteger groupCount = new AtomicInteger(0);
this.log.log("Finished gathering data, writing file...");
List<? extends Group> groups = this.plugin.getGroupManager().getAll().values().stream()
// export groups in order of weight
.sorted((o1, o2) -> {
int i = Integer.compare(o2.getWeight().orElse(0), o1.getWeight().orElse(0));
return i != 0 ? i : o1.getName().compareToIgnoreCase(o2.getName());
}).collect(Collectors.toList());
for (Group group : groups) {
if (!group.getName().equals(GroupManager.DEFAULT_GROUP_NAME)) {
write(writer, "/lp creategroup " + group.getName());
}
}
for (Group group : groups) {
if (groupCount.get() == 0) {
write(writer, "");
}
write(writer, "# Export group: " + group.getName());
for (Node node : group.normalData().immutable().values()) {
write(writer, "/lp " + NodeCommandFactory.generateCommand(node, group.getName(), HolderType.GROUP, true, false));
}
write(writer, "");
this.log.logAllProgress("Exported {} groups so far.", groupCount.incrementAndGet());
}
this.log.log("Exported " + groupCount.get() + " groups.");
write(writer, "");
write(writer, "");
// Export tracks
this.log.log("Starting track export.");
Collection<? extends Track> tracks = this.plugin.getTrackManager().getAll().values();
if (!tracks.isEmpty()) {
// Create the actual tracks first
write(writer, "# Create tracks");
for (Track track : tracks) {
write(writer, "/lp createtrack " + track.getName());
}
write(writer, "");
AtomicInteger trackCount = new AtomicInteger(0);
for (Track track : this.plugin.getTrackManager().getAll().values()) {
write(writer, "# Export track: " + track.getName());
for (String group : track.getGroups()) {
write(writer, "/lp track " + track.getName() + " append " + group);
}
write(writer, "");
this.log.logAllProgress("Exported {} tracks so far.", trackCount.incrementAndGet());
}
write(writer, "");
write(writer, "");
}
this.log.log("Exported " + tracks.size() + " tracks.");
if (this.includeUsers) {
// Users are migrated in separate threads.
// This is because there are likely to be a lot of them, and because we can.
// It's a big speed improvement, since the database/files are split up and can handle concurrent reads.
this.log.log("Starting user export. Finding a list of unique users to export.");
// Find all of the unique users we need to export
Storage ds = this.plugin.getStorage();
Set<UUID> users = ds.getUniqueUsers().join();
this.log.log("Found " + users.size() + " unique users to export.");
write(writer, "# Export users");
// create a threadpool to process the users concurrently
ExecutorService executor = Executors.newFixedThreadPool(32);
// Setup a file writing lock. We don't want multiple threads writing at the same time.
// The write function accepts a list of strings, as we want a user's data to be grouped together.
// This means it can be processed and added in one go.
ReentrantLock lock = new ReentrantLock();
Consumer<List<String>> writeFunction = strings -> {
lock.lock();
try {
for (String s : strings) {
write(writer, s);
}
} finally {
lock.unlock();
}
};
// A set of futures, which are really just the processes we need to wait for.
Set<CompletableFuture<Void>> futures = new HashSet<>();
AtomicInteger userCount = new AtomicInteger(0);
// iterate through each user.
for (UUID uuid : users) {
// register a task for the user, and schedule it's execution with the pool
futures.add(CompletableFuture.runAsync(() -> {
// actually export the user. this output will be fed to the writing function when we have all of the user's data.
List<String> output = new ArrayList<>();
User user = this.plugin.getStorage().loadUser(uuid, null).join();
output.add("# Export user: " + user.getUniqueId().toString() + " - " + user.getUsername().orElse("unknown username"));
boolean inDefault = false;
for (Node node : user.normalData().immutable().values()) {
if (NodeType.INHERITANCE.tryCast(node).map(n -> n.getGroupName().equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME)).orElse(false)) {
inDefault = true;
continue;
}
output.add("/lp " + NodeCommandFactory.generateCommand(node, user.getUniqueId().toString(), HolderType.USER, true, false));
}
if (!user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME).equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME)) {
output.add("/lp user " + user.getUniqueId().toString() + " switchprimarygroup " + user.getPrimaryGroup().getStoredValue().get());
}
if (!inDefault) {
output.add("/lp user " + user.getUniqueId().toString() + " parent remove default");
}
this.plugin.getUserManager().cleanup(user);
writeFunction.accept(output);
userCount.incrementAndGet();
}, executor));
}
// all of the threads have been scheduled now and are running. we just need to wait for them all to complete
CompletableFuture<Void> overallFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
while (true) {
try {
overallFuture.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
// abnormal error - just break
e.printStackTrace();
break;
} catch (TimeoutException e) {
// still executing - send a progress report and continue waiting
this.log.logAllProgress("Exported {} users so far.", userCount.get());
continue;
}
// process is complete
break;
}
executor.shutdown();
this.log.log("Exported " + userCount.get() + " users.");
}
writer.flush();
this.log.getListeners().forEach(l -> Message.LOG_EXPORT_SUCCESS.send(l, this.filePath.toFile().getAbsolutePath()));
} catch (Exception e) {
try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(Files.newOutputStream(this.filePath)), StandardCharsets.UTF_8))) {
GsonProvider.prettyPrinting().toJson(file, out);
} catch (IOException e) {
e.printStackTrace();
}
this.log.getListeners().forEach(l -> Message.LOG_EXPORT_SUCCESS.send(l, this.filePath.toFile().getAbsolutePath()));
}
private JsonObject exportGroups() {
JsonObject out = new JsonObject();
List<Group> groups = this.plugin.getGroupManager().getAll().values().stream()
.sorted((o1, o2) -> {
int i = Integer.compare(o2.getWeight().orElse(0), o1.getWeight().orElse(0));
return i != 0 ? i : o1.getName().compareToIgnoreCase(o2.getName());
}).collect(Collectors.toList());
for (Group group : groups) {
out.add(group.getName(), new JObject()
.add("nodes", NodeJsonSerializer.serializeNodes(group.normalData().asSet()))
.toJson());
}
return out;
}
private JsonObject exportTracks() {
JsonObject out = new JsonObject();
Collection<? extends Track> tracks = this.plugin.getTrackManager().getAll().values();
for (Track track : tracks) {
out.add(track.getName(), new JObject()
.add("groups", new JArray().consume(arr -> track.getGroups().forEach(arr::add)))
.toJson());
}
return out;
}
private JsonObject exportUsers() {
// Users are migrated in separate threads.
// This is because there are likely to be a lot of them, and because we can.
// It's a big speed improvement, since the database/files are split up and can handle concurrent reads.
this.log.log("Finding a list of unique users to export.");
// Find all of the unique users we need to export
Storage ds = this.plugin.getStorage();
Set<UUID> users = ds.getUniqueUsers().join();
this.log.log("Found " + users.size() + " unique users to export.");
// create a threadpool to process the users concurrently
ExecutorService executor = Executors.newFixedThreadPool(32);
// A set of futures, which are really just the processes we need to wait for.
Set<CompletableFuture<Void>> futures = new HashSet<>();
AtomicInteger userCount = new AtomicInteger(0);
Map<UUID, JsonObject> out = Collections.synchronizedMap(new HashMap<>());
// iterate through each user.
for (UUID uuid : users) {
// register a task for the user, and schedule it's execution with the pool
futures.add(CompletableFuture.runAsync(() -> {
User user = this.plugin.getStorage().loadUser(uuid, null).join();
out.put(user.getUniqueId(), new JObject()
.consume(obj -> {
user.getUsername().ifPresent(username -> obj.add("username", username));
if (!user.getPrimaryGroup().getStoredValue().orElse(GroupManager.DEFAULT_GROUP_NAME).equalsIgnoreCase(GroupManager.DEFAULT_GROUP_NAME)) {
obj.add("primaryGroup", user.getPrimaryGroup().getStoredValue().get());
}
})
.add("nodes", NodeJsonSerializer.serializeNodes(user.normalData().asSet()))
.toJson());
this.plugin.getUserManager().cleanup(user);
userCount.incrementAndGet();
}, executor));
}
// all of the threads have been scheduled now and are running. we just need to wait for them all to complete
CompletableFuture<Void> overallFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
while (true) {
try {
overallFuture.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
// abnormal error - just break
e.printStackTrace();
break;
} catch (TimeoutException e) {
// still executing - send a progress report and continue waiting
this.log.logAllProgress("Exported {} users so far.", userCount.get());
continue;
}
// process is complete
break;
}
executor.shutdown();
JsonObject outJson = new JsonObject();
for (Map.Entry<UUID, JsonObject> entry : out.entrySet()) {
outJson.add(entry.getKey().toString(), entry.getValue());
}
return outJson;
}
}

View File

@ -25,24 +25,31 @@
package me.lucko.luckperms.common.backup;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.command.CommandManager;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.sender.DummySender;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.utils.NodeJsonSerializer;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.luckperms.api.event.cause.CreationCause;
import net.luckperms.api.model.DataType;
import net.luckperms.api.node.Node;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@ -50,35 +57,59 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* Handles import operations
*/
public class Importer implements Runnable {
private final CommandManager commandManager;
private final LuckPermsPlugin plugin;
private final Set<Sender> notify;
private final List<String> commandList;
private final List<ImportCommand> commands;
private final JsonObject data;
public Importer(CommandManager commandManager, Sender executor, List<String> commands) {
this.commandManager = commandManager;
public Importer(LuckPermsPlugin plugin, Sender executor, JsonObject data) {
this.plugin = plugin;
if (executor.isConsole()) {
this.notify = ImmutableSet.of(executor);
} else {
this.notify = ImmutableSet.of(executor, commandManager.getPlugin().getConsoleSender());
this.notify = ImmutableSet.of(executor, plugin.getConsoleSender());
}
this.commandList = commands.stream()
.map(String::trim)
.filter(s -> !s.isEmpty())
.filter(s -> !s.startsWith("#"))
.filter(s -> !s.startsWith("//"))
.map(s -> s.startsWith("/luckperms ") ? s.substring("/luckperms ".length()) : s)
.map(s -> s.startsWith("/lp ") ? s.substring("/lp ".length()) : s)
.collect(Collectors.toList());
this.commands = new ArrayList<>();
this.data = data;
}
private static final class UserData {
private final String username;
private final String primaryGroup;
private final Set<Node> nodes;
UserData(String username, String primaryGroup, Set<Node> nodes) {
this.username = username;
this.primaryGroup = primaryGroup;
this.nodes = nodes;
}
}
private void processGroup(String groupName, Set<Node> nodes) {
Group group = this.plugin.getStorage().createAndLoadGroup(groupName, CreationCause.INTERNAL).join();
group.setNodes(DataType.NORMAL, nodes);
this.plugin.getStorage().saveGroup(group);
}
private void processTrack(String trackName, List<String> groups) {
Track track = this.plugin.getStorage().createAndLoadTrack(trackName, CreationCause.INTERNAL).join();
track.setGroups(groups);
this.plugin.getStorage().saveTrack(track).join();
}
private void processUser(UUID uuid, UserData userData) {
User user = this.plugin.getStorage().loadUser(uuid, userData.username).join();
if (userData.primaryGroup != null) {
user.getPrimaryGroup().setStoredValue(userData.primaryGroup);
}
user.setNodes(DataType.NORMAL, userData.nodes);
this.plugin.getStorage().saveUser(user).join();
this.plugin.getUserManager().cleanup(user);
}
@Override
@ -87,28 +118,39 @@ public class Importer implements Runnable {
this.notify.forEach(s -> Message.IMPORT_START.send(s));
// start an update task in the background - we'll #join this later
CompletableFuture<Void> updateTask = CompletableFuture.runAsync(() -> this.commandManager.getPlugin().getSyncTaskBuffer().requestDirectly());
CompletableFuture<Void> updateTask = CompletableFuture.runAsync(() -> this.plugin.getSyncTaskBuffer().requestDirectly());
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "Processing commands..."));
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "Reading data..."));
// form instances for all commands, and register them
int index = 1;
for (String command : this.commandList) {
ImportCommand cmd = new ImportCommand(this.commandManager, index, command);
this.commands.add(cmd);
Map<String, Set<Node>> groups = new HashMap<>();
Map<String, List<String>> tracks = new HashMap<>();
Map<UUID, UserData> users = new HashMap<>();
if (cmd.getCommand().startsWith("creategroup ") || cmd.getCommand().startsWith("createtrack ")) {
cmd.process(); // process immediately
for (Map.Entry<String, JsonElement> group : this.data.get("groups").getAsJsonObject().entrySet()) {
groups.put(group.getKey(), NodeJsonSerializer.deserializeNodes(group.getValue().getAsJsonObject().get("nodes").getAsJsonArray()));
}
for (Map.Entry<String, JsonElement> track : this.data.get("tracks").getAsJsonObject().entrySet()) {
JsonArray trackGroups = track.getValue().getAsJsonObject().get("groups").getAsJsonArray();
List<String> trackGroupsList = new ArrayList<>();
trackGroups.forEach(g -> trackGroupsList.add(g.getAsString()));
tracks.put(track.getKey(), trackGroupsList);
}
for (Map.Entry<String, JsonElement> user : this.data.get("users").getAsJsonObject().entrySet()) {
JsonObject jsonData = user.getValue().getAsJsonObject();
UUID uuid = UUID.fromString(user.getKey());
String username = null;
String primaryGroup = null;
Set<Node> nodes = NodeJsonSerializer.deserializeNodes(jsonData.get("nodes").getAsJsonArray());
if (jsonData.has("username")) {
username = jsonData.get("username").getAsString();
}
if (jsonData.has("primaryGroup")) {
primaryGroup = jsonData.get("primaryGroup").getAsString();
}
index++;
}
// split data up into sections for each holder
// holder id --> commands
ListMultimap<String, ImportCommand> sections = MultimapBuilder.linkedHashKeys().arrayListValues().build();
for (ImportCommand cmd : this.commands) {
sections.put(Strings.nullToEmpty(cmd.getTarget()), cmd);
users.put(uuid, new UserData(username, primaryGroup, nodes));
}
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "Waiting for initial update task to complete..."));
@ -116,34 +158,43 @@ public class Importer implements Runnable {
// join the update task future before scheduling command executions
updateTask.join();
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "Setting up command executor..."));
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "Setting up data processor..."));
// create a threadpool for the processing
ExecutorService executor = Executors.newFixedThreadPool(128, new ThreadFactoryBuilder().setNameFormat("luckperms-importer-%d").build());
ExecutorService executor = Executors.newFixedThreadPool(16, new ThreadFactoryBuilder().setNameFormat("luckperms-importer-%d").build());
// A set of futures, which are really just the processes we need to wait for.
Set<CompletableFuture<Void>> futures = new HashSet<>();
int total = 0;
AtomicInteger processedCount = new AtomicInteger(0);
// iterate through each user sublist.
for (Collection<ImportCommand> subList : sections.asMap().values()) {
// register and start a new thread to process the sublist
futures.add(CompletableFuture.completedFuture(subList).thenAcceptAsync(sl -> {
// iterate through each user in the sublist, and grab their data.
for (ImportCommand cmd : sl) {
cmd.process();
processedCount.incrementAndGet();
}
for (Map.Entry<String, Set<Node>> group : groups.entrySet()) {
futures.add(CompletableFuture.completedFuture(group).thenAcceptAsync(ent -> {
processGroup(ent.getKey(), ent.getValue());
processedCount.incrementAndGet();
}, executor));
total++;
}
for (Map.Entry<String, List<String>> track : tracks.entrySet()) {
futures.add(CompletableFuture.completedFuture(track).thenAcceptAsync(ent -> {
processTrack(ent.getKey(), ent.getValue());
processedCount.incrementAndGet();
}, executor));
total++;
}
for (Map.Entry<UUID, UserData> user : users.entrySet()) {
futures.add(CompletableFuture.completedFuture(user).thenAcceptAsync(ent -> {
processUser(ent.getKey(), ent.getValue());
processedCount.incrementAndGet();
}, executor));
total++;
}
// all of the threads have been scheduled now and are running. we just need to wait for them all to complete
CompletableFuture<Void> overallFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "All commands have been processed and scheduled - now waiting for the execution to complete."));
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "All data entries have been processed and scheduled for import - now waiting for the execution to complete."));
while (true) {
try {
@ -154,7 +205,7 @@ public class Importer implements Runnable {
break;
} catch (TimeoutException e) {
// still executing - send a progress report and continue waiting
sendProgress(processedCount.get());
sendProgress(processedCount.get(), total);
continue;
}
@ -167,166 +218,12 @@ public class Importer implements Runnable {
long endTime = System.currentTimeMillis();
double seconds = (endTime - startTime) / 1000.0;
int errors = (int) this.commands.stream().filter(v -> v.getResult().wasFailure()).count();
switch (errors) {
case 0:
this.notify.forEach(s -> Message.IMPORT_END_COMPLETE.send(s, seconds));
break;
case 1:
this.notify.forEach(s -> Message.IMPORT_END_COMPLETE_ERR_SIN.send(s, seconds, errors));
break;
default:
this.notify.forEach(s -> Message.IMPORT_END_COMPLETE_ERR.send(s, seconds, errors));
break;
}
AtomicInteger errIndex = new AtomicInteger(1);
for (ImportCommand e : this.commands) {
if (e.getResult() != null && e.getResult().wasFailure()) {
for (Sender s : this.notify) {
Message.IMPORT_END_ERROR_HEADER.send(s, errIndex.get(), e.getId(), e.getCommand(), e.getResult().toString());
e.getOutput().forEach(out -> Message.IMPORT_END_ERROR_CONTENT.send(s, out));
Message.IMPORT_END_ERROR_FOOTER.send(s);
}
errIndex.incrementAndGet();
}
}
this.notify.forEach(s -> Message.IMPORT_END_COMPLETE.send(s, seconds));
}
private void sendProgress(int processedCount) {
int percent = (processedCount * 100) / this.commandList.size();
int errors = (int) this.commands.stream().filter(v -> v.isCompleted() && v.getResult().wasFailure()).count();
if (errors == 1) {
this.notify.forEach(s -> Message.IMPORT_PROGRESS_SIN.send(s, percent, processedCount, this.commands.size(), errors));
} else {
this.notify.forEach(s -> Message.IMPORT_PROGRESS.send(s, percent, processedCount, this.commands.size(), errors));
}
}
private static class ImportCommand extends DummySender {
private static final Splitter ARGUMENT_SPLITTER = Splitter.on(CommandManager.COMMAND_SEPARATOR_PATTERN).omitEmptyStrings();
private static final Splitter SPACE_SPLITTER = Splitter.on(" ");
private final CommandManager commandManager;
private final int id;
private final String command;
private final String target;
private boolean completed = false;
private final List<String> output = new ArrayList<>();
private CommandResult result = CommandResult.FAILURE;
ImportCommand(CommandManager commandManager, int id, String command) {
super(commandManager.getPlugin());
this.commandManager = commandManager;
this.id = id;
this.command = command;
this.target = determineTarget(command);
}
@Override
protected void consumeMessage(String s) {
this.output.add(s);
}
public void process() {
if (isCompleted()) {
return;
}
try {
List<String> args = CommandManager.stripQuotes(ARGUMENT_SPLITTER.splitToList(getCommand()));
CommandResult result = this.commandManager.onCommand(this, "lp", args, Runnable::run).get();
setResult(result);
} catch (Exception e) {
setResult(CommandResult.FAILURE);
e.printStackTrace();
}
setCompleted(true);
}
private static String determineTarget(String command) {
if (command.startsWith("user ") && command.length() > "user ".length()) {
String subCmd = command.substring("user ".length());
if (!subCmd.contains(" ")) {
return null;
}
String targetUser = SPACE_SPLITTER.split(subCmd).iterator().next();
return "u:" + targetUser;
}
if (command.startsWith("group ") && command.length() > "group ".length()) {
String subCmd = command.substring("group ".length());
if (!subCmd.contains(" ")) {
return null;
}
String targetGroup = SPACE_SPLITTER.split(subCmd).iterator().next();
return "g:" + targetGroup;
}
if (command.startsWith("track ") && command.length() > "track ".length()) {
String subCmd = command.substring("track ".length());
if (!subCmd.contains(" ")) {
return null;
}
String targetTrack = SPACE_SPLITTER.split(subCmd).iterator().next();
return "t:" + targetTrack;
}
if (command.startsWith("creategroup ") && command.length() > "creategroup ".length()) {
String targetGroup = command.substring("creategroup ".length());
return "g:" + targetGroup;
}
if (command.startsWith("createtrack ") && command.length() > "createtrack ".length()) {
String targetTrack = command.substring("createtrack ".length());
return "t:" + targetTrack;
}
return null;
}
public int getId() {
return this.id;
}
public String getCommand() {
return this.command;
}
public String getTarget() {
return this.target;
}
public boolean isCompleted() {
return this.completed;
}
public List<String> getOutput() {
return this.output;
}
public CommandResult getResult() {
return this.result;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
public void setResult(CommandResult result) {
this.result = result;
}
private void sendProgress(int processedCount, int total) {
int percent = (processedCount * 100) / total;
this.notify.forEach(s -> Message.IMPORT_PROGRESS.send(s, percent, processedCount, total, 0));
}
}

View File

@ -0,0 +1,332 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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 me.lucko.luckperms.common.backup;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import me.lucko.luckperms.common.command.CommandManager;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.sender.DummySender;
import me.lucko.luckperms.common.sender.Sender;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* Handles import operations
*/
public class LegacyImporter implements Runnable {
private final CommandManager commandManager;
private final Set<Sender> notify;
private final List<String> commandList;
private final List<ImportCommand> commands;
public LegacyImporter(CommandManager commandManager, Sender executor, List<String> commands) {
this.commandManager = commandManager;
if (executor.isConsole()) {
this.notify = ImmutableSet.of(executor);
} else {
this.notify = ImmutableSet.of(executor, commandManager.getPlugin().getConsoleSender());
}
this.commandList = commands.stream()
.map(String::trim)
.filter(s -> !s.isEmpty())
.filter(s -> !s.startsWith("#"))
.filter(s -> !s.startsWith("//"))
.map(s -> s.startsWith("/luckperms ") ? s.substring("/luckperms ".length()) : s)
.map(s -> s.startsWith("/lp ") ? s.substring("/lp ".length()) : s)
.collect(Collectors.toList());
this.commands = new ArrayList<>();
}
@Override
public void run() {
long startTime = System.currentTimeMillis();
this.notify.forEach(s -> Message.IMPORT_START.send(s));
// start an update task in the background - we'll #join this later
CompletableFuture<Void> updateTask = CompletableFuture.runAsync(() -> this.commandManager.getPlugin().getSyncTaskBuffer().requestDirectly());
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "Processing commands..."));
// form instances for all commands, and register them
int index = 1;
for (String command : this.commandList) {
ImportCommand cmd = new ImportCommand(this.commandManager, index, command);
this.commands.add(cmd);
if (cmd.getCommand().startsWith("creategroup ") || cmd.getCommand().startsWith("createtrack ")) {
cmd.process(); // process immediately
}
index++;
}
// split data up into sections for each holder
// holder id --> commands
ListMultimap<String, ImportCommand> sections = MultimapBuilder.linkedHashKeys().arrayListValues().build();
for (ImportCommand cmd : this.commands) {
sections.put(Strings.nullToEmpty(cmd.getTarget()), cmd);
}
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "Waiting for initial update task to complete..."));
// join the update task future before scheduling command executions
updateTask.join();
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "Setting up command executor..."));
// create a threadpool for the processing
ExecutorService executor = Executors.newFixedThreadPool(128, new ThreadFactoryBuilder().setNameFormat("luckperms-importer-%d").build());
// A set of futures, which are really just the processes we need to wait for.
Set<CompletableFuture<Void>> futures = new HashSet<>();
AtomicInteger processedCount = new AtomicInteger(0);
// iterate through each user sublist.
for (Collection<ImportCommand> subList : sections.asMap().values()) {
// register and start a new thread to process the sublist
futures.add(CompletableFuture.completedFuture(subList).thenAcceptAsync(sl -> {
// iterate through each user in the sublist, and grab their data.
for (ImportCommand cmd : sl) {
cmd.process();
processedCount.incrementAndGet();
}
}, executor));
}
// all of the threads have been scheduled now and are running. we just need to wait for them all to complete
CompletableFuture<Void> overallFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
this.notify.forEach(s -> Message.IMPORT_INFO.send(s, "All commands have been processed and scheduled - now waiting for the execution to complete."));
while (true) {
try {
overallFuture.get(2, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
// abnormal error - just break
e.printStackTrace();
break;
} catch (TimeoutException e) {
// still executing - send a progress report and continue waiting
sendProgress(processedCount.get());
continue;
}
// process is complete
break;
}
executor.shutdown();
long endTime = System.currentTimeMillis();
double seconds = (endTime - startTime) / 1000.0;
int errors = (int) this.commands.stream().filter(v -> v.getResult().wasFailure()).count();
switch (errors) {
case 0:
this.notify.forEach(s -> Message.IMPORT_END_COMPLETE.send(s, seconds));
break;
case 1:
this.notify.forEach(s -> Message.IMPORT_END_COMPLETE_ERR_SIN.send(s, seconds, errors));
break;
default:
this.notify.forEach(s -> Message.IMPORT_END_COMPLETE_ERR.send(s, seconds, errors));
break;
}
AtomicInteger errIndex = new AtomicInteger(1);
for (ImportCommand e : this.commands) {
if (e.getResult() != null && e.getResult().wasFailure()) {
for (Sender s : this.notify) {
Message.IMPORT_END_ERROR_HEADER.send(s, errIndex.get(), e.getId(), e.getCommand(), e.getResult().toString());
e.getOutput().forEach(out -> Message.IMPORT_END_ERROR_CONTENT.send(s, out));
Message.IMPORT_END_ERROR_FOOTER.send(s);
}
errIndex.incrementAndGet();
}
}
}
private void sendProgress(int processedCount) {
int percent = (processedCount * 100) / this.commandList.size();
int errors = (int) this.commands.stream().filter(v -> v.isCompleted() && v.getResult().wasFailure()).count();
if (errors == 1) {
this.notify.forEach(s -> Message.IMPORT_PROGRESS_SIN.send(s, percent, processedCount, this.commands.size(), errors));
} else {
this.notify.forEach(s -> Message.IMPORT_PROGRESS.send(s, percent, processedCount, this.commands.size(), errors));
}
}
private static class ImportCommand extends DummySender {
private static final Splitter ARGUMENT_SPLITTER = Splitter.on(CommandManager.COMMAND_SEPARATOR_PATTERN).omitEmptyStrings();
private static final Splitter SPACE_SPLITTER = Splitter.on(" ");
private final CommandManager commandManager;
private final int id;
private final String command;
private final String target;
private boolean completed = false;
private final List<String> output = new ArrayList<>();
private CommandResult result = CommandResult.FAILURE;
ImportCommand(CommandManager commandManager, int id, String command) {
super(commandManager.getPlugin());
this.commandManager = commandManager;
this.id = id;
this.command = command;
this.target = determineTarget(command);
}
@Override
protected void consumeMessage(String s) {
this.output.add(s);
}
public void process() {
if (isCompleted()) {
return;
}
try {
List<String> args = CommandManager.stripQuotes(ARGUMENT_SPLITTER.splitToList(getCommand()));
CommandResult result = this.commandManager.onCommand(this, "lp", args, Runnable::run).get();
setResult(result);
} catch (Exception e) {
setResult(CommandResult.FAILURE);
e.printStackTrace();
}
setCompleted(true);
}
private static String determineTarget(String command) {
if (command.startsWith("user ") && command.length() > "user ".length()) {
String subCmd = command.substring("user ".length());
if (!subCmd.contains(" ")) {
return null;
}
String targetUser = SPACE_SPLITTER.split(subCmd).iterator().next();
return "u:" + targetUser;
}
if (command.startsWith("group ") && command.length() > "group ".length()) {
String subCmd = command.substring("group ".length());
if (!subCmd.contains(" ")) {
return null;
}
String targetGroup = SPACE_SPLITTER.split(subCmd).iterator().next();
return "g:" + targetGroup;
}
if (command.startsWith("track ") && command.length() > "track ".length()) {
String subCmd = command.substring("track ".length());
if (!subCmd.contains(" ")) {
return null;
}
String targetTrack = SPACE_SPLITTER.split(subCmd).iterator().next();
return "t:" + targetTrack;
}
if (command.startsWith("creategroup ") && command.length() > "creategroup ".length()) {
String targetGroup = command.substring("creategroup ".length());
return "g:" + targetGroup;
}
if (command.startsWith("createtrack ") && command.length() > "createtrack ".length()) {
String targetTrack = command.substring("createtrack ".length());
return "t:" + targetTrack;
}
return null;
}
public int getId() {
return this.id;
}
public String getCommand() {
return this.command;
}
public String getTarget() {
return this.target;
}
public boolean isCompleted() {
return this.completed;
}
public List<String> getOutput() {
return this.output;
}
public CommandResult getResult() {
return this.result;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
public void setResult(CommandResult result) {
this.result = result;
}
}
}

View File

@ -61,11 +61,11 @@ import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.query.QueryOptions;
import java.util.ArrayList;
@ -276,16 +276,9 @@ public class CommandManager {
.forEach(c -> {
String permission = c.getPermission().map(CommandPermission::getPermission).orElse("None");
TextComponent component = TextUtils.fromLegacy("&3> &a" + String.format(c.getUsage(), label), AMPERSAND_CHAR)
TextComponent component = LegacyComponentSerializer.INSTANCE.deserialize("&3> &a" + String.format(c.getUsage(), label), AMPERSAND_CHAR)
.toBuilder().applyDeep(comp -> {
comp.hoverEvent(HoverEvent.showText(TextUtils.fromLegacy(TextUtils.joinNewline(
"&bCommand: &2" + c.getName(),
"&bDescription: &2" + c.getDescription(),
"&bUsage: &2" + String.format(c.getUsage(), label),
"&bPermission: &2" + permission,
" ",
"&7Click to auto-complete."
), AMPERSAND_CHAR)));
comp.hoverEvent(HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize(String.join("\n", "&bCommand: &2" + c.getName(), "&bDescription: &2" + c.getDescription(), "&bUsage: &2" + String.format(c.getUsage(), label), "&bPermission: &2" + permission, " ", "&7Click to auto-complete."), AMPERSAND_CHAR)));
comp.clickEvent(ClickEvent.suggestCommand(String.format(c.getUsage(), label)));
}).build();
sender.sendMessage(component);

View File

@ -45,10 +45,10 @@ import me.lucko.luckperms.common.node.types.Suffix;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.DataMutateResult;
import net.luckperms.api.model.DataType;
@ -90,10 +90,7 @@ public class MetaAddChatMeta extends SharedSubCommand {
DataMutateResult result = holder.setNode(DataType.NORMAL, ((this.type == ChatMetaType.PREFIX ? Prefix.builder(priority, meta) : Suffix.builder(priority, meta))).withContext(context).build(), true);
if (result.wasSuccessful()) {
TextComponent.Builder builder = Message.ADD_CHATMETA_SUCCESS.asComponent(plugin.getLocaleManager(), holder.getFormattedDisplayName(), this.type.name().toLowerCase(), meta, priority, MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder();
HoverEvent event = HoverEvent.showText(TextUtils.fromLegacy(
"¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta,
'¥'
));
HoverEvent event = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize("¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta, '¥'));
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());

View File

@ -47,10 +47,10 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.DataType;
import net.luckperms.api.model.TemporaryDataMutateResult;
@ -99,10 +99,7 @@ public class MetaAddTempChatMeta extends SharedSubCommand {
duration = ret.getMergedNode().getExpiry().getEpochSecond();
TextComponent.Builder builder = Message.ADD_TEMP_CHATMETA_SUCCESS.asComponent(plugin.getLocaleManager(), holder.getFormattedDisplayName(), this.type.name().toLowerCase(), meta, priority, DurationFormatter.LONG.formatDateDiff(duration), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder();
HoverEvent event = HoverEvent.showText(TextUtils.fromLegacy(
"¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta,
'¥'
));
HoverEvent event = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize("¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta, '¥'));
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());

View File

@ -43,12 +43,12 @@ import me.lucko.luckperms.common.node.factory.NodeCommandFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.ComponentBuilder;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata;
@ -171,11 +171,7 @@ public class MetaInfo extends SharedSubCommand {
}
}
HoverEvent hoverEvent = HoverEvent.showText(TextUtils.fromLegacy(TextUtils.joinNewline(
"¥3> ¥a" + node.getPriority() + " ¥7- ¥r" + node.getMetaValue(),
" ",
"¥7Click to remove this " + node.getMetaType().name().toLowerCase() + " from " + holder.getFormattedDisplayName()
), '¥'));
HoverEvent hoverEvent = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize(String.join("\n", "¥3> ¥a" + node.getPriority() + " ¥7- ¥r" + node.getMetaValue(), " ", "¥7Click to remove this " + node.getMetaType().name().toLowerCase() + " from " + holder.getFormattedDisplayName()), '¥'));
String id = holder.getType() == HolderType.GROUP ? holder.getObjectName() : holder.getFormattedDisplayName();
boolean explicitGlobalContext = !holder.getPlugin().getConfiguration().getContextsFile().getDefaultContexts().isEmpty();
@ -198,11 +194,7 @@ public class MetaInfo extends SharedSubCommand {
}
}
HoverEvent hoverEvent = HoverEvent.showText(TextUtils.fromLegacy(TextUtils.joinNewline(
"¥3> ¥r" + node.getMetaKey() + " ¥7- ¥r" + node.getMetaValue(),
" ",
"¥7Click to remove this meta pair from " + holder.getFormattedDisplayName()
), '¥'));
HoverEvent hoverEvent = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize(String.join("\n", "¥3> ¥r" + node.getMetaKey() + " ¥7- ¥r" + node.getMetaValue(), " ", "¥7Click to remove this meta pair from " + holder.getFormattedDisplayName()), '¥'));
String id = holder.getType() == HolderType.GROUP ? holder.getObjectName() : holder.getFormattedDisplayName();
boolean explicitGlobalContext = !holder.getPlugin().getConfiguration().getContextsFile().getDefaultContexts().isEmpty();

View File

@ -45,10 +45,10 @@ import me.lucko.luckperms.common.node.types.Suffix;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.model.DataMutateResult;
import net.luckperms.api.model.DataType;
@ -107,10 +107,7 @@ public class MetaRemoveChatMeta extends SharedSubCommand {
if (result.wasSuccessful()) {
TextComponent.Builder builder = Message.REMOVE_CHATMETA_SUCCESS.asComponent(plugin.getLocaleManager(), holder.getFormattedDisplayName(), this.type.name().toLowerCase(), meta, priority, MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder();
HoverEvent event = HoverEvent.showText(TextUtils.fromLegacy(
"¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta,
'¥'
));
HoverEvent event = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize("¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta, '¥'));
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());

View File

@ -45,10 +45,10 @@ import me.lucko.luckperms.common.node.types.Suffix;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.model.DataMutateResult;
import net.luckperms.api.model.DataType;
@ -107,10 +107,7 @@ public class MetaRemoveTempChatMeta extends SharedSubCommand {
if (result.wasSuccessful()) {
TextComponent.Builder builder = Message.REMOVE_TEMP_CHATMETA_SUCCESS.asComponent(plugin.getLocaleManager(), holder.getFormattedDisplayName(), this.type.name().toLowerCase(), meta, priority, MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder();
HoverEvent event = HoverEvent.showText(TextUtils.fromLegacy(
"¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta,
'¥'
));
HoverEvent event = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize("¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta, '¥'));
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());

View File

@ -44,10 +44,10 @@ import me.lucko.luckperms.common.node.types.Meta;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.DataType;
import net.luckperms.api.node.Node;
@ -90,10 +90,7 @@ public class MetaSet extends SharedSubCommand {
holder.setNode(DataType.NORMAL, node, true);
TextComponent.Builder builder = Message.SET_META_SUCCESS.asComponent(plugin.getLocaleManager(), key, value, holder.getFormattedDisplayName(), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder();
HoverEvent event = HoverEvent.showText(TextUtils.fromLegacy(
TextUtils.joinNewline("¥3Raw key: ¥r" + key, "¥3Raw value: ¥r" + value),
'¥'
));
HoverEvent event = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize(String.join("\n", "¥3Raw key: ¥r" + key, "¥3Raw value: ¥r" + value), '¥'));
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());

View File

@ -47,10 +47,10 @@ import me.lucko.luckperms.common.node.types.Suffix;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.DataMutateResult;
import net.luckperms.api.model.DataType;
@ -126,10 +126,7 @@ public class MetaSetChatMeta extends SharedSubCommand {
DataMutateResult result = holder.setNode(DataType.NORMAL, ((this.type == ChatMetaType.PREFIX ? Prefix.builder(priority, meta) : Suffix.builder(priority, meta))).withContext(context).build(), true);
if (result.wasSuccessful()) {
TextComponent.Builder builder = Message.ADD_CHATMETA_SUCCESS.asComponent(plugin.getLocaleManager(), holder.getFormattedDisplayName(), this.type.name().toLowerCase(), meta, priority, MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder();
HoverEvent event = HoverEvent.showText(TextUtils.fromLegacy(
"¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta,
'¥'
));
HoverEvent event = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize("¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta, '¥'));
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());

View File

@ -46,10 +46,10 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.DataType;
import net.luckperms.api.model.TemporaryMergeBehaviour;
@ -95,10 +95,7 @@ public class MetaSetTemp extends SharedSubCommand {
duration = holder.setNode(DataType.NORMAL, node, modifier).getMergedNode().getExpiry().getEpochSecond();
TextComponent.Builder builder = Message.SET_META_TEMP_SUCCESS.asComponent(plugin.getLocaleManager(), key, value, holder.getFormattedDisplayName(), DurationFormatter.LONG.formatDateDiff(duration), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder();
HoverEvent event = HoverEvent.showText(TextUtils.fromLegacy(
TextUtils.joinNewline("¥3Raw key: ¥r" + key, "¥3Raw value: ¥r" + value),
'¥'
));
HoverEvent event = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize(String.join("\n", "¥3Raw key: ¥r" + key, "¥3Raw value: ¥r" + value), '¥'));
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());

View File

@ -49,10 +49,10 @@ import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.model.DataType;
import net.luckperms.api.model.TemporaryDataMutateResult;
@ -139,10 +139,7 @@ public class MetaSetTempChatMeta extends SharedSubCommand {
duration = ret.getMergedNode().getExpiry().getEpochSecond();
TextComponent.Builder builder = Message.ADD_TEMP_CHATMETA_SUCCESS.asComponent(plugin.getLocaleManager(), holder.getFormattedDisplayName(), this.type.name().toLowerCase(), meta, priority, DurationFormatter.LONG.formatDateDiff(duration), MessageUtils.contextSetToString(plugin.getLocaleManager(), context)).toBuilder();
HoverEvent event = HoverEvent.showText(TextUtils.fromLegacy(
"¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta,
'¥'
));
HoverEvent event = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize("¥3Raw " + this.type.name().toLowerCase() + ": ¥r" + meta, '¥'));
builder.applyDeep(c -> c.hoverEvent(event));
sender.sendMessage(builder.build());

View File

@ -46,12 +46,12 @@ import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import me.lucko.luckperms.common.util.Iterators;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.ComponentBuilder;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.types.InheritanceNode;
import net.luckperms.api.query.QueryOptions;
@ -119,7 +119,7 @@ public class ParentInfo extends SharedSubCommand {
s += "\n&2 expires in " + DurationFormatter.LONG.formatDateDiff(node.getExpiry().getEpochSecond());
}
TextComponent message = TextUtils.fromLegacy(s, CommandManager.AMPERSAND_CHAR).toBuilder().applyDeep(makeFancy(holder, label, node)).build();
TextComponent message = LegacyComponentSerializer.INSTANCE.deserialize(s, CommandManager.AMPERSAND_CHAR).toBuilder().applyDeep(makeFancy(holder, label, node)).build();
sender.sendMessage(message);
}
@ -137,11 +137,7 @@ public class ParentInfo extends SharedSubCommand {
};
private static Consumer<ComponentBuilder<? ,?>> makeFancy(PermissionHolder holder, String label, InheritanceNode node) {
HoverEvent hoverEvent = HoverEvent.showText(TextUtils.fromLegacy(TextUtils.joinNewline(
"&3> &f" + node.getGroupName(),
" ",
"&7Click to remove this parent from " + holder.getFormattedDisplayName()
), CommandManager.AMPERSAND_CHAR));
HoverEvent hoverEvent = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize(String.join("\n", "&3> &f" + node.getGroupName(), " ", "&7Click to remove this parent from " + holder.getFormattedDisplayName()), CommandManager.AMPERSAND_CHAR));
String id = holder.getType() == HolderType.GROUP ? holder.getObjectName() : holder.getFormattedDisplayName();
boolean explicitGlobalContext = !holder.getPlugin().getConfiguration().getContextsFile().getDefaultContexts().isEmpty();

View File

@ -46,12 +46,12 @@ import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import me.lucko.luckperms.common.util.Iterators;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.ComponentBuilder;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
@ -119,7 +119,7 @@ public class PermissionInfo extends SharedSubCommand {
s += "\n&2- expires in " + DurationFormatter.LONG.formatDateDiff(node.getExpiry().getEpochSecond());
}
TextComponent message = TextUtils.fromLegacy(s, CommandManager.AMPERSAND_CHAR).toBuilder().applyDeep(makeFancy(holder, label, node)).build();
TextComponent message = LegacyComponentSerializer.INSTANCE.deserialize(s, CommandManager.AMPERSAND_CHAR).toBuilder().applyDeep(makeFancy(holder, label, node)).build();
sender.sendMessage(message);
}
@ -137,11 +137,8 @@ public class PermissionInfo extends SharedSubCommand {
};
private static Consumer<ComponentBuilder<?, ?>> makeFancy(PermissionHolder holder, String label, Node node) {
HoverEvent hoverEvent = HoverEvent.showText(TextUtils.fromLegacy(TextUtils.joinNewline(
"¥3> " + (node.getValue() ? "¥a" : "¥c") + node.getKey(),
" ",
"¥7Click to remove this node from " + holder.getFormattedDisplayName()
), '¥'));
String[] strings = new String[]{"¥3> " + (node.getValue() ? "¥a" : "¥c") + node.getKey(), " ", "¥7Click to remove this node from " + holder.getFormattedDisplayName()};
HoverEvent hoverEvent = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize(String.join("\n", strings), '¥'));
String id = holder.getType() == HolderType.GROUP ? holder.getObjectName() : holder.getFormattedDisplayName();
boolean explicitGlobalContext = !holder.getPlugin().getConfiguration().getContextsFile().getDefaultContexts().isEmpty();

View File

@ -43,7 +43,7 @@ import me.lucko.luckperms.common.locale.command.CommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.node.comparator.HeldPermissionComparator;
import me.lucko.luckperms.common.node.comparator.HeldNodeComparator;
import me.lucko.luckperms.common.node.factory.NodeCommandFactory;
import me.lucko.luckperms.common.node.types.Inheritance;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@ -51,12 +51,12 @@ import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import me.lucko.luckperms.common.util.Iterators;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.ComponentBuilder;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.HeldNode;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.InheritanceNode;
@ -127,7 +127,7 @@ public class GroupListMembers extends SubCommand<Group> {
private static <T extends Comparable<T>> void sendResult(Sender sender, List<HeldNode<T>> results, Function<T, String> lookupFunction, Message headerMessage, HolderType holderType, String label, int page) {
results = new ArrayList<>(results);
results.sort(HeldPermissionComparator.normal());
results.sort(HeldNodeComparator.normal());
int pageIndex = page - 1;
List<List<HeldNode<T>>> pages = Iterators.divideIterable(results, 15);
@ -148,7 +148,7 @@ public class GroupListMembers extends SubCommand<Group> {
for (Map.Entry<String, HeldNode<T>> ent : mappedContent) {
String s = "&3> &b" + ent.getKey() + " " + getNodeExpiryString(ent.getValue().getNode()) + MessageUtils.getAppendableNodeContextString(sender.getPlugin().getLocaleManager(), ent.getValue().getNode());
TextComponent message = TextUtils.fromLegacy(s, CommandManager.AMPERSAND_CHAR).toBuilder().applyDeep(makeFancy(ent.getKey(), holderType, label, ent.getValue(), sender.getPlugin())).build();
TextComponent message = LegacyComponentSerializer.INSTANCE.deserialize(s, CommandManager.AMPERSAND_CHAR).toBuilder().applyDeep(makeFancy(ent.getKey(), holderType, label, ent.getValue(), sender.getPlugin())).build();
sender.sendMessage(message);
}
}
@ -162,11 +162,7 @@ public class GroupListMembers extends SubCommand<Group> {
}
private static Consumer<ComponentBuilder<? ,?>> makeFancy(String holderName, HolderType holderType, String label, HeldNode<?> perm, LuckPermsPlugin plugin) {
HoverEvent hoverEvent = HoverEvent.showText(TextUtils.fromLegacy(TextUtils.joinNewline(
"&3> &b" + ((InheritanceNode) perm.getNode()).getGroupName(),
" ",
"&7Click to remove this parent from " + holderName
), CommandManager.AMPERSAND_CHAR));
HoverEvent hoverEvent = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize(String.join("\n", "&3> &b" + ((InheritanceNode) perm.getNode()).getGroupName(), " ", "&7Click to remove this parent from " + holderName), CommandManager.AMPERSAND_CHAR));
boolean explicitGlobalContext = !plugin.getConfiguration().getContextsFile().getDefaultContexts().isEmpty();
String command = "/" + label + " " + NodeCommandFactory.generateCommand(perm.getNode(), holderName, holderType, false, explicitGlobalContext);

View File

@ -44,6 +44,7 @@ import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.manager.group.GroupManager;
import me.lucko.luckperms.common.node.utils.NodeJsonSerializer;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
@ -162,7 +163,7 @@ public class ApplyEditsCommand extends SingleCommand {
}
Set<Node> before = new HashSet<>(holder.normalData().immutable().values());
Set<Node> after = new HashSet<>(WebEditor.deserializePermissions(data.getAsJsonArray("nodes")));
Set<Node> after = new HashSet<>(NodeJsonSerializer.deserializeNodes(data.getAsJsonArray("nodes")));
Map.Entry<Set<Node>, Set<Node>> diff = diff(before, after);
Set<Node> diffAdded = diff.getKey();

View File

@ -57,9 +57,9 @@ public class ExportCommand extends SingleCommand {
}
Path dataDirectory = plugin.getBootstrap().getDataDirectory();
Path path = dataDirectory.resolve(args.get(0));
Path path = dataDirectory.resolve(args.get(0) + ".json.gz");
if (!path.getParent().equals(dataDirectory) || path.getFileName().toString().equals("config.yml")) {
if (!path.getParent().equals(dataDirectory)) {
Message.FILE_NOT_WITHIN_DIRECTORY.send(sender, path.toString());
return CommandResult.INVALID_ARGS;
}

View File

@ -25,7 +25,10 @@
package me.lucko.luckperms.common.commands.misc;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.backup.Importer;
import me.lucko.luckperms.common.backup.LegacyImporter;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.SingleCommand;
import me.lucko.luckperms.common.command.access.CommandPermission;
@ -35,13 +38,17 @@ import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.GZIPInputStream;
public class ImportCommand extends SingleCommand {
private final AtomicBoolean running = new AtomicBoolean(false);
@ -57,8 +64,13 @@ public class ImportCommand extends SingleCommand {
return CommandResult.STATE_ERROR;
}
String fileName = args.get(0);
if (!fileName.contains(".")) {
fileName += ".json.gz";
}
Path dataDirectory = plugin.getBootstrap().getDataDirectory();
Path path = dataDirectory.resolve(args.get(0));
Path path = dataDirectory.resolve(fileName);
if (!path.getParent().equals(dataDirectory) || path.getFileName().toString().equals("config.yml")) {
Message.FILE_NOT_WITHIN_DIRECTORY.send(sender, path.toString());
@ -66,40 +78,68 @@ public class ImportCommand extends SingleCommand {
}
if (!Files.exists(path)) {
Message.IMPORT_LOG_DOESNT_EXIST.send(sender, path.toString());
Message.IMPORT_FILE_DOESNT_EXIST.send(sender, path.toString());
return CommandResult.INVALID_ARGS;
}
if (!Files.isReadable(path)) {
Message.IMPORT_LOG_NOT_READABLE.send(sender, path.toString());
Message.IMPORT_FILE_NOT_READABLE.send(sender, path.toString());
return CommandResult.FAILURE;
}
List<String> commands;
try {
commands = Files.readAllLines(path, StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
Message.IMPORT_LOG_FAILURE.send(sender);
return CommandResult.FAILURE;
}
if (!this.running.compareAndSet(false, true)) {
Message.IMPORT_ALREADY_RUNNING.send(sender);
return CommandResult.STATE_ERROR;
}
Importer importer = new Importer(plugin.getCommandManager(), sender, commands);
// Run the importer in its own thread.
plugin.getBootstrap().getScheduler().executeAsync(() -> {
if (!fileName.endsWith(".json.gz")) {
// legacy
List<String> commands;
try {
importer.run();
} finally {
this.running.set(false);
commands = Files.readAllLines(path, StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
Message.IMPORT_FILE_READ_FAILURE.send(sender);
return CommandResult.FAILURE;
}
});
if (!this.running.compareAndSet(false, true)) {
Message.IMPORT_ALREADY_RUNNING.send(sender);
return CommandResult.STATE_ERROR;
}
LegacyImporter importer = new LegacyImporter(plugin.getCommandManager(), sender, commands);
// Run the importer in its own thread.
plugin.getBootstrap().getScheduler().executeAsync(() -> {
try {
importer.run();
} finally {
this.running.set(false);
}
});
} else {
// modern
JsonObject data;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(Files.newInputStream(path)), StandardCharsets.UTF_8))) {
data = GsonProvider.normal().fromJson(reader, JsonObject.class);;
} catch (IOException e) {
e.printStackTrace();
Message.IMPORT_FILE_READ_FAILURE.send(sender);
return CommandResult.FAILURE;
}
if (!this.running.compareAndSet(false, true)) {
Message.IMPORT_ALREADY_RUNNING.send(sender);
return CommandResult.STATE_ERROR;
}
Importer importer = new Importer(plugin, sender, data);
// Run the importer in its own thread.
plugin.getBootstrap().getScheduler().executeAsync(() -> {
try {
importer.run();
} finally {
this.running.set(false);
}
});
}
return CommandResult.SUCCESS;
}

View File

@ -44,19 +44,19 @@ import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.node.comparator.HeldPermissionComparator;
import me.lucko.luckperms.common.node.comparator.HeldNodeComparator;
import me.lucko.luckperms.common.node.factory.NodeCommandFactory;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import me.lucko.luckperms.common.util.Iterators;
import me.lucko.luckperms.common.util.Predicates;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.ComponentBuilder;
import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.HeldNode;
import net.luckperms.api.node.Node;
@ -129,7 +129,7 @@ public class SearchCommand extends SingleCommand {
private static <T extends Comparable<T>> void sendResult(Sender sender, List<HeldNode<T>> results, Function<T, String> lookupFunction, Message headerMessage, HolderType holderType, String label, int page, Comparison comparison) {
results = new ArrayList<>(results);
results.sort(HeldPermissionComparator.normal());
results.sort(HeldNodeComparator.normal());
int pageIndex = page - 1;
List<List<HeldNode<T>>> pages = Iterators.divideIterable(results, 15);
@ -156,7 +156,7 @@ public class SearchCommand extends SingleCommand {
}
String s = "&3> &b" + ent.getKey() + permission + "&7 - " + (ent.getValue().getNode().getValue() ? "&a" : "&c") + ent.getValue().getNode().getValue() + getNodeExpiryString(ent.getValue().getNode()) + MessageUtils.getAppendableNodeContextString(sender.getPlugin().getLocaleManager(), ent.getValue().getNode());
TextComponent message = TextUtils.fromLegacy(s, CommandManager.AMPERSAND_CHAR).toBuilder().applyDeep(makeFancy(ent.getKey(), holderType, label, ent.getValue(), sender.getPlugin())).build();
TextComponent message = LegacyComponentSerializer.INSTANCE.deserialize(s, CommandManager.AMPERSAND_CHAR).toBuilder().applyDeep(makeFancy(ent.getKey(), holderType, label, ent.getValue(), sender.getPlugin())).build();
sender.sendMessage(message);
}
}
@ -170,11 +170,8 @@ public class SearchCommand extends SingleCommand {
}
private static Consumer<ComponentBuilder<?, ?>> makeFancy(String holderName, HolderType holderType, String label, HeldNode<?> perm, LuckPermsPlugin plugin) {
HoverEvent hoverEvent = HoverEvent.showText(TextUtils.fromLegacy(TextUtils.joinNewline(
"&3> " + (perm.getNode().getValue() ? "&a" : "&c") + perm.getNode().getKey(),
" ",
"&7Click to remove this node from " + holderName
), CommandManager.AMPERSAND_CHAR));
String[] strings = new String[]{"&3> " + (perm.getNode().getValue() ? "&a" : "&c") + perm.getNode().getKey(), " ", "&7Click to remove this node from " + holderName};
HoverEvent hoverEvent = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize(String.join("\n", strings), CommandManager.AMPERSAND_CHAR));
boolean explicitGlobalContext = !plugin.getConfiguration().getContextsFile().getDefaultContexts().isEmpty();
String command = "/" + label + " " + NodeCommandFactory.generateCommand(perm.getNode(), holderName, holderType, false, explicitGlobalContext);

View File

@ -28,9 +28,9 @@ package me.lucko.luckperms.common.locale.message;
import me.lucko.luckperms.common.command.CommandManager;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -453,9 +453,9 @@ public enum Message {
IMPORT_ALREADY_RUNNING("&cAnother import process is already running. Please wait for it to finish and try again.", true),
EXPORT_ALREADY_RUNNING("&cAnother export process is already running. Please wait for it to finish and try again.", true),
FILE_NOT_WITHIN_DIRECTORY("&cError: File &4{}&c must be a direct child of the data directory.", true),
IMPORT_LOG_DOESNT_EXIST("&cError: File &4{}&c does not exist.", true),
IMPORT_LOG_NOT_READABLE("&cError: File &4{}&c is not readable.", true),
IMPORT_LOG_FAILURE("&cAn unexpected error occured whilst reading from the log file.", true),
IMPORT_FILE_DOESNT_EXIST("&cError: File &4{}&c does not exist.", true),
IMPORT_FILE_NOT_READABLE("&cError: File &4{}&c is not readable.", true),
IMPORT_FILE_READ_FAILURE("&cAn unexpected error occured whilst reading from the import file.", true),
IMPORT_PROGRESS("&b(Import) &b-> &f{}&f% complete &7- &b{}&f/&b{} &foperations complete with &c{} &ferrors.", true),
IMPORT_PROGRESS_SIN("&b(Import) &b-> &f{}&f% complete &7- &b{}&f/&b{} &foperations complete with &c{} &ferror.", true),
@ -482,10 +482,18 @@ public enum Message {
Message(String message, boolean showPrefix) {
// rewrite hardcoded placeholders according to their position
this.message = TextUtils.rewritePlaceholders(message);
this.message = rewritePlaceholders(message);
this.showPrefix = showPrefix;
}
private static String rewritePlaceholders(String input) {
int i = 0;
while (input.contains("{}")) {
input = input.replaceFirst("\\{\\}", "{" + i++ + "}");
}
return input;
}
public String getMessage() {
return this.message;
}
@ -517,7 +525,7 @@ public enum Message {
}
public TextComponent asComponent(@Nullable LocaleManager localeManager, Object... objects) {
return TextUtils.fromLegacy(format(localeManager, objects), CommandManager.AMPERSAND_CHAR);
return LegacyComponentSerializer.INSTANCE.deserialize(format(localeManager, objects), CommandManager.AMPERSAND_CHAR);
}
public void send(Sender sender, Object... objects) {

View File

@ -29,14 +29,14 @@ import net.luckperms.api.node.HeldNode;
import java.util.Comparator;
public class HeldPermissionComparator<T extends Comparable<T>> implements Comparator<HeldNode<T>> {
public class HeldNodeComparator<T extends Comparable<T>> implements Comparator<HeldNode<T>> {
public static <T extends Comparable<T>> Comparator<? super HeldNode<T>> normal() {
return new HeldPermissionComparator<>();
return new HeldNodeComparator<>();
}
public static <T extends Comparable<T>> Comparator<? super HeldNode<T>> reverse() {
return HeldPermissionComparator.<T>normal().reversed();
return HeldNodeComparator.<T>normal().reversed();
}
@Override

View File

@ -0,0 +1,69 @@
package me.lucko.luckperms.common.node.utils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.context.ContextSetJsonSerializer;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeBuilder;
import java.time.Instant;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
public class NodeJsonSerializer {
private NodeJsonSerializer() {
}
public static JsonArray serializeNodes(Collection<Node> nodes) {
JsonArray arr = new JsonArray();
for (Node node : nodes) {
JsonObject attributes = new JsonObject();
attributes.addProperty("type", node.getType().name().toLowerCase());
attributes.addProperty("key", node.getKey());
attributes.addProperty("value", node.getValue());
Instant expiry = node.getExpiry();
if (expiry != null) {
attributes.addProperty("expiry", expiry.getEpochSecond());
}
if (!node.getContexts().isEmpty()) {
attributes.add("context", ContextSetJsonSerializer.serializeContextSet(node.getContexts()));
}
arr.add(attributes);
}
return arr;
}
public static Set<Node> deserializeNodes(JsonArray arr) {
Set<Node> nodes = new HashSet<>();
for (JsonElement ent : arr) {
JsonObject attributes = ent.getAsJsonObject();
String key = attributes.get("key").getAsString();
boolean value = attributes.get("value").getAsBoolean();
NodeBuilder<?, ?> builder = NodeBuilders.determineMostApplicable(key).value(value);
if (attributes.has("expiry")) {
builder.expiry(attributes.get("expiry").getAsLong());
}
if (attributes.has("context")) {
builder.context(ContextSetJsonSerializer.deserializeContextSet(attributes.get("context")));
}
nodes.add(builder.build());
}
return nodes;
}
}

View File

@ -28,9 +28,9 @@ package me.lucko.luckperms.common.sender;
import com.google.common.base.Splitter;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.Tristate;
import java.lang.ref.WeakReference;
@ -93,7 +93,7 @@ public final class AbstractSender<T> implements Sender {
@Override
public void sendMessage(Component message) {
if (isConsole()) {
sendMessage(TextUtils.toLegacy(message));
sendMessage(LegacyComponentSerializer.INSTANCE.serialize(message));
return;
}

View File

@ -26,9 +26,9 @@
package me.lucko.luckperms.common.sender;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.Tristate;
import java.util.UUID;
@ -58,7 +58,7 @@ public abstract class DummySender implements Sender {
@Override
public void sendMessage(Component message) {
consumeMessage(TextUtils.toLegacy(message));
consumeMessage(LegacyComponentSerializer.INSTANCE.serialize(message));
}
@Override

View File

@ -1,71 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* 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 me.lucko.luckperms.common.util;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class TextUtils {
private TextUtils() {}
public static String joinNewline(String... strings) {
return joinNewline(Arrays.stream(strings));
}
public static String joinNewline(Stream<String> strings) {
return strings.collect(Collectors.joining("\n"));
}
public static TextComponent fromLegacy(String input, char character) {
return LegacyComponentSerializer.INSTANCE.deserialize(input, character);
}
public static TextComponent fromLegacy(String input) {
return LegacyComponentSerializer.INSTANCE.deserialize(input);
}
public static String toLegacy(Component component, char character) {
return LegacyComponentSerializer.INSTANCE.serialize(component, character);
}
public static String toLegacy(Component component) {
return LegacyComponentSerializer.INSTANCE.serialize(component);
}
public static String rewritePlaceholders(String input) {
int i = 0;
while (input.contains("{}")) {
input = input.replaceFirst("\\{\\}", "{" + i++ + "}");
}
return input;
}
}

View File

@ -34,7 +34,6 @@ import me.lucko.luckperms.common.locale.message.Message;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.DurationFormatter;
import me.lucko.luckperms.common.util.StackTracePrinter;
import me.lucko.luckperms.common.util.TextUtils;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import me.lucko.luckperms.common.util.gson.JArray;
import me.lucko.luckperms.common.util.gson.JObject;
@ -46,6 +45,7 @@ import me.lucko.luckperms.common.web.BytebinClient;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.Tristate;
import net.luckperms.api.query.QueryMode;
@ -60,6 +60,7 @@ import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
/**
@ -232,7 +233,7 @@ public class VerboseListener {
}
// send the message
HoverEvent hoverEvent = HoverEvent.showText(TextUtils.fromLegacy(TextUtils.joinNewline(hover.stream()), CommandManager.AMPERSAND_CHAR));
HoverEvent hoverEvent = HoverEvent.showText(LegacyComponentSerializer.INSTANCE.deserialize(hover.stream().collect(Collectors.joining("\n")), CommandManager.AMPERSAND_CHAR));
TextComponent text = textComponent.toBuilder().applyDeep(comp -> comp.hoverEvent(hoverEvent)).build();
this.notifiedSender.sendMessage(text);
}

View File

@ -26,23 +26,18 @@
package me.lucko.luckperms.common.web;
import com.google.common.base.Preconditions;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.context.ContextSetJsonSerializer;
import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import me.lucko.luckperms.common.node.utils.NodeJsonSerializer;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import me.lucko.luckperms.common.util.gson.JArray;
import me.lucko.luckperms.common.util.gson.JObject;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeBuilder;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
@ -52,11 +47,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Utility methods for interacting with the LuckPerms web permission editor.
@ -69,7 +60,7 @@ public final class WebEditor {
.add("type", holder.getType().toString())
.add("id", holder.getObjectName())
.add("displayName", holder.getPlainDisplayName())
.add("nodes", serializePermissions(holder.normalData().immutable().values()));
.add("nodes", NodeJsonSerializer.serializeNodes(holder.normalData().immutable().values()));
}
private static JObject writeData(Track track) {
@ -143,50 +134,4 @@ public final class WebEditor {
}
}
private static JsonArray serializePermissions(Collection<Node> nodes) {
JsonArray arr = new JsonArray();
for (Node node : nodes) {
JsonObject attributes = new JsonObject();
attributes.addProperty("type", node.getType().name().toLowerCase());
attributes.addProperty("key", node.getKey());
attributes.addProperty("value", node.getValue());
Instant expiry = node.getExpiry();
if (expiry != null) {
attributes.addProperty("expiry", expiry.getEpochSecond());
}
if (!node.getContexts().isEmpty()) {
attributes.add("context", ContextSetJsonSerializer.serializeContextSet(node.getContexts()));
}
arr.add(attributes);
}
return arr;
}
public static Set<Node> deserializePermissions(JsonArray arr) {
Set<Node> nodes = new HashSet<>();
for (JsonElement ent : arr) {
JsonObject attributes = ent.getAsJsonObject();
String key = attributes.get("key").getAsString();
boolean value = attributes.get("value").getAsBoolean();
NodeBuilder<?, ?> builder = NodeBuilders.determineMostApplicable(key).value(value);
if (attributes.has("expiry")) {
builder.expiry(attributes.get("expiry").getAsLong());
}
if (attributes.has("context")) {
builder.context(ContextSetJsonSerializer.deserializeContextSet(attributes.get("context")));
}
nodes.add(builder.build());
}
return nodes;
}
}

View File

@ -28,9 +28,9 @@ package me.lucko.luckperms.nukkit;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.sender.SenderFactory;
import me.lucko.luckperms.common.util.TextUtils;
import net.kyori.text.Component;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.Tristate;
import cn.nukkit.Player;
@ -75,7 +75,7 @@ public class NukkitSenderFactory extends SenderFactory<CommandSender> {
@Override
protected void sendMessage(CommandSender sender, Component message) {
// Fallback to legacy format
sendMessage(sender, TextUtils.toLegacy(message));
sendMessage(sender, LegacyComponentSerializer.INSTANCE.serialize(message));
}
@Override

View File

@ -31,10 +31,10 @@ import com.velocitypowered.api.proxy.Player;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import me.lucko.luckperms.common.sender.SenderFactory;
import me.lucko.luckperms.common.util.TextUtils;
import me.lucko.luckperms.velocity.service.CompatibilityUtil;
import net.kyori.text.Component;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import net.luckperms.api.node.Tristate;
import java.util.UUID;
@ -62,7 +62,7 @@ public class VelocitySenderFactory extends SenderFactory<CommandSource> {
@Override
protected void sendMessage(CommandSource source, String s) {
sendMessage(source, TextUtils.fromLegacy(s));
sendMessage(source, LegacyComponentSerializer.INSTANCE.deserialize(s));
}
@Override