use of me.lucko.luckperms.common.storage.Storage in project LuckPerms by lucko.
the class Exporter method run.
@Override
public void run() {
try (BufferedWriter writer = Files.newBufferedWriter(this.filePath, StandardCharsets.UTF_8)) {
this.log.log("Starting.");
write(writer, "# LuckPerms Export File");
write(writer, "# Generated by " + this.executor.getNameWithLocation() + " at " + DATE_FORMAT.format(new Date(System.currentTimeMillis())));
write(writer, "");
// Export Groups
this.log.log("Starting group export.");
// Create the actual groups first
write(writer, "# Create groups");
AtomicInteger groupCount = new AtomicInteger(0);
List<? extends 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) {
if (!group.getName().equals(NodeFactory.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.getEnduringNodes().values()) {
write(writer, "/lp " + NodeFactory.nodeAsCommand(node, group.getName(), HolderType.GROUP, true));
}
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.");
// 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");
// divide into 16 pools.
Cycle<List<UUID>> userPools = new Cycle<>(nInstances(32, ArrayList::new));
for (UUID uuid : users) {
userPools.next().add(uuid);
}
this.log.log("Split users into " + userPools.getBacking().size() + " threads for export.");
// 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 threads we need to wait for.
Set<CompletableFuture<Void>> futures = new HashSet<>();
AtomicInteger userCount = new AtomicInteger(0);
// iterate through each user sublist.
for (List<UUID> subList : userPools.getBacking()) {
// register and start a new thread to process the sublist
futures.add(CompletableFuture.runAsync(() -> {
// iterate through each user in the sublist, and grab their data.
for (UUID uuid : subList) {
try {
// 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.getUuid().toString() + " - " + user.getName().orElse("unknown username"));
boolean inDefault = false;
for (Node node : user.getEnduringNodes().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));
}
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");
}
this.plugin.getUserManager().cleanup(user);
writeFunction.accept(output);
this.log.logProgress("Exported {} users so far.", userCount.incrementAndGet());
} catch (Exception e) {
e.printStackTrace();
}
}
}, this.plugin.getBootstrap().getScheduler().async()));
}
// all of the threads have been scheduled now and are running. we just need to wait for them all to complete
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()])).join();
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) {
e.printStackTrace();
}
}
Aggregations