diff --git a/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java b/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java index c8380b556..bf04db0cf 100644 --- a/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java +++ b/common/src/main/java/me/lucko/luckperms/common/backup/Exporter.java @@ -79,12 +79,14 @@ public class Exporter implements Runnable { private final LuckPermsPlugin plugin; private final Sender executor; private final Path filePath; + private final boolean includeUsers; private final ProgressLogger log; - public Exporter(LuckPermsPlugin plugin, Sender executor, Path filePath) { + public Exporter(LuckPermsPlugin plugin, Sender executor, Path filePath, boolean includeUsers) { this.plugin = plugin; this.executor = executor; this.filePath = filePath; + this.includeUsers = includeUsers; this.log = new ProgressLogger(null, Message.EXPORT_LOG, Message.EXPORT_LOG_PROGRESS); this.log.addListener(plugin.getConsoleSender()); @@ -169,101 +171,103 @@ public class Exporter implements Runnable { 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. - // 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."); - 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 users = ds.getUniqueUsers().join(); + this.log.log("Found " + users.size() + " unique users to export."); - // Find all of the unique users we need to export - Storage ds = this.plugin.getStorage(); - Set users = ds.getUniqueUsers().join(); - this.log.log("Found " + users.size() + " unique users to export."); + write(writer, "# Export users"); - write(writer, "# Export users"); + // create a threadpool to process the users concurrently + ExecutorService executor = Executors.newFixedThreadPool(32); - // 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> writeFunction = strings -> { - lock.lock(); - try { - for (String s : strings) { - write(writer, s); + // 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> writeFunction = strings -> { + lock.lock(); + try { + for (String s : strings) { + write(writer, s); + } + } finally { + lock.unlock(); } - } finally { - lock.unlock(); - } - }; + }; - // A set of futures, which are really just the processes we need to wait for. - Set> futures = new HashSet<>(); + // A set of futures, which are really just the processes we need to wait for. + Set> futures = new HashSet<>(); - AtomicInteger userCount = new AtomicInteger(0); + 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 output = new ArrayList<>(); + // 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 output = new ArrayList<>(); - User user = this.plugin.getStorage().loadUser(uuid, null).join(); - output.add("# Export user: " + user.getUuid().toString() + " - " + user.getName().orElse("unknown username")); + User user = this.plugin.getStorage().loadUser(uuid, null).join(); + output.add("# Export user: " + user.getUuid().toString() + " - " + user.getName().orElse("unknown username")); - boolean inDefault = false; - for (Node node : user.enduringData().immutable().values()) { - if (node.isGroupNode() && node.getGroupName().equalsIgnoreCase(NodeFactory.DEFAULT_GROUP_NAME)) { - inDefault = true; - continue; + boolean inDefault = false; + for (Node node : user.enduringData().immutable().values()) { + if (node.isGroupNode() && node.getGroupName().equalsIgnoreCase(NodeFactory.DEFAULT_GROUP_NAME)) { + inDefault = true; + continue; + } + + output.add("/lp " + NodeFactory.nodeAsCommand(node, user.getUuid().toString(), HolderType.USER, true)); } - output.add("/lp " + NodeFactory.nodeAsCommand(node, user.getUuid().toString(), HolderType.USER, true)); - } + if (!user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME).equalsIgnoreCase(NodeFactory.DEFAULT_GROUP_NAME)) { + output.add("/lp user " + user.getUuid().toString() + " switchprimarygroup " + user.getPrimaryGroup().getStoredValue().get()); + } - if (!user.getPrimaryGroup().getStoredValue().orElse(NodeFactory.DEFAULT_GROUP_NAME).equalsIgnoreCase(NodeFactory.DEFAULT_GROUP_NAME)) { - output.add("/lp user " + user.getUuid().toString() + " switchprimarygroup " + user.getPrimaryGroup().getStoredValue().get()); - } + if (!inDefault) { + output.add("/lp user " + user.getUuid().toString() + " parent remove default"); + } - if (!inDefault) { - output.add("/lp user " + user.getUuid().toString() + " parent remove default"); - } + this.plugin.getUserManager().cleanup(user); + writeFunction.accept(output); - 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 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; + userCount.incrementAndGet(); + }, executor)); } - // process is complete - break; + // all of the threads have been scheduled now and are running. we just need to wait for them all to complete + CompletableFuture 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."); } - 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())); diff --git a/common/src/main/java/me/lucko/luckperms/common/commands/misc/ExportCommand.java b/common/src/main/java/me/lucko/luckperms/common/commands/misc/ExportCommand.java index 12ab09bce..ce61ffa55 100644 --- a/common/src/main/java/me/lucko/luckperms/common/commands/misc/ExportCommand.java +++ b/common/src/main/java/me/lucko/luckperms/common/commands/misc/ExportCommand.java @@ -46,7 +46,7 @@ public class ExportCommand extends SingleCommand { private final AtomicBoolean running = new AtomicBoolean(false); public ExportCommand(LocaleManager locale) { - super(CommandSpec.EXPORT.localize(locale), "Export", CommandPermission.EXPORT, Predicates.not(1)); + super(CommandSpec.EXPORT.localize(locale), "Export", CommandPermission.EXPORT, Predicates.notInRange(1, 2)); } @Override @@ -57,6 +57,8 @@ public class ExportCommand extends SingleCommand { } Path path = plugin.getBootstrap().getDataDirectory().resolve(args.get(0)); + boolean includeUsers = !args.remove("--without-users"); + if (Files.exists(path)) { Message.LOG_EXPORT_ALREADY_EXISTS.send(sender, path.toString()); return CommandResult.INVALID_ARGS; @@ -80,7 +82,7 @@ public class ExportCommand extends SingleCommand { return CommandResult.STATE_ERROR; } - Exporter exporter = new Exporter(plugin, sender, path); + Exporter exporter = new Exporter(plugin, sender, path, includeUsers); // Run the exporter in its own thread. plugin.getBootstrap().getScheduler().doAsync(() -> {