9
0
mirror of https://github.com/WiIIiam278/HuskSync.git synced 2025-12-25 09:39:18 +00:00

Add /userdata dump command, for file/web dumping of user data json

This commit is contained in:
William
2022-10-07 22:52:58 +01:00
parent cbf5d9c24e
commit 7536bfaaf5
18 changed files with 276 additions and 14 deletions

View File

@@ -15,6 +15,7 @@ import net.william278.husksync.util.ResourceReader;
import net.william278.desertwell.Version;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -134,6 +135,14 @@ public interface HuskSync {
@NotNull
Version getPluginVersion();
/**
* Returns the plugin data folder
*
* @return the plugin data folder as a {@link File}
*/
@NotNull
File getDataFolder();
/**
* Returns a future returning the latest plugin {@link Version} if the plugin is out-of-date
*

View File

@@ -154,7 +154,7 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
public List<String> onTabComplete(@NotNull String[] args) {
if (args.length <= 1) {
return Arrays.stream(SUB_COMMANDS)
.filter(argument -> argument.startsWith(args.length >= 1 ? args[0] : ""))
.filter(argument -> argument.startsWith(args.length == 1 ? args[0] : ""))
.sorted().collect(Collectors.toList());
}
return Collections.emptyList();

View File

@@ -41,6 +41,11 @@ public enum Permission {
*/
COMMAND_USER_DATA_MANAGE("husksync.command.userdata.manage", DefaultAccess.OPERATORS),
/**
* Lets the user dump user data to a file or the web {@code /userdata dump (player) (version_uuid)}
*/
COMMAND_USER_DATA_DUMP("husksync.command.userdata.dump", DefaultAccess.OPERATORS),
/*
* /inventory command permissions
*/

View File

@@ -3,18 +3,21 @@ package net.william278.husksync.command;
import net.william278.husksync.HuskSync;
import net.william278.husksync.data.DataSaveCause;
import net.william278.husksync.player.OnlineUser;
import net.william278.husksync.util.DataDumper;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.stream.Collectors;
public class UserDataCommand extends CommandBase implements TabCompletable {
private final String[] COMMAND_ARGUMENTS = {"view", "list", "delete", "restore", "pin"};
private final String[] COMMAND_ARGUMENTS = {"view", "list", "delete", "restore", "pin", "dump"};
public UserDataCommand(@NotNull HuskSync implementor) {
super("userdata", Permission.COMMAND_USER_DATA, implementor, "playerdata");
@@ -24,7 +27,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
if (args.length < 1) {
plugin.getLocales().getLocale("error_invalid_syntax",
"/userdata <view/list/delete/restore/pin> <username> [version_uuid]")
"/userdata <view/list/delete/restore/pin/dump> <username> [version_uuid]")
.ifPresent(player::sendMessage);
return;
}
@@ -199,6 +202,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
.ifPresent(player::sendMessage);
return;
}
final String username = args[1];
try {
final UUID versionUuid = UUID.fromString(args[2]);
@@ -233,6 +237,45 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
.ifPresent(player::sendMessage);
}
}
case "dump" -> {
if (!player.hasPermission(Permission.COMMAND_USER_DATA_DUMP.node)) {
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
return;
}
if (args.length < 3) {
plugin.getLocales().getLocale("error_invalid_syntax",
"/userdata dump <username> <version_uuid>")
.ifPresent(player::sendMessage);
return;
}
final boolean toWeb = args.length > 3 && args[3].equalsIgnoreCase("web");
final String username = args[1];
try {
final UUID versionUuid = UUID.fromString(args[2]);
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
optionalUser -> optionalUser.ifPresentOrElse(
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(
optionalUserData -> optionalUserData.ifPresentOrElse(userData -> {
try {
final DataDumper dumper = DataDumper.create(userData, user, plugin);
final String result = toWeb ? dumper.toWeb() : dumper.toFile();
plugin.getLocales().getLocale("data_dumped", versionUuid.toString()
.split("-")[0], user.username, result)
.ifPresent(player::sendMessage);
} catch (IOException e) {
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to dump user data", e);
}
}, () -> plugin.getLocales().getLocale("error_invalid_version_uuid")
.ifPresent(player::sendMessage))),
() -> plugin.getLocales().getLocale("error_invalid_player")
.ifPresent(player::sendMessage))));
} catch (IllegalArgumentException e) {
plugin.getLocales().getLocale("error_invalid_syntax",
"/userdata dump <username> <version_uuid>")
.ifPresent(player::sendMessage);
}
}
}
}

View File

@@ -128,6 +128,11 @@ public class DataEditor {
dataOwner.username, userData.versionUUID().toString())
.ifPresent(user::sendMessage);
}
if (user.hasPermission(Permission.COMMAND_USER_DATA_DUMP.node)) {
locales.getLocale("data_manager_system_buttons",
dataOwner.username, userData.versionUUID().toString())
.ifPresent(user::sendMessage);
}
}
@NotNull

View File

@@ -0,0 +1,172 @@
package net.william278.husksync.util;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.william278.husksync.HuskSync;
import net.william278.husksync.data.UserDataSnapshot;
import net.william278.husksync.player.User;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.StringJoiner;
import java.util.logging.Level;
/**
* Utility class for dumping {@link UserDataSnapshot}s to a file
*/
public class DataDumper {
private static final String LOGS_SITE_ENDPOINT = "https://api.mclo.gs/1/log";
private final HuskSync plugin;
private final UserDataSnapshot dataSnapshot;
private final User user;
private DataDumper(@NotNull UserDataSnapshot dataSnapshot,
@NotNull User user, @NotNull HuskSync implementor) {
this.dataSnapshot = dataSnapshot;
this.user = user;
this.plugin = implementor;
}
/**
* Create a {@link DataDumper} of the given {@link UserDataSnapshot}
*
* @param dataSnapshot The {@link UserDataSnapshot} to dump
* @param user The {@link User} whose data is being dumped
* @param plugin The implementing {@link HuskSync} plugin
* @return A {@link DataDumper} for the given {@link UserDataSnapshot}
*/
public static DataDumper create(@NotNull UserDataSnapshot dataSnapshot,
@NotNull User user, @NotNull HuskSync plugin) {
return new DataDumper(dataSnapshot, user, plugin);
}
/**
* Dumps the data snapshot to a string
*
* @return the data snapshot as a string
*/
@Override
@NotNull
public String toString() {
return plugin.getDataAdapter().toJson(dataSnapshot.userData(), true);
}
@NotNull
public String toWeb() {
try {
final URL url = new URL(LOGS_SITE_ENDPOINT);
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
// Dispatch the request
final byte[] messageBody = getWebContentField().getBytes(StandardCharsets.UTF_8);
final int messageLength = messageBody.length;
connection.setFixedLengthStreamingMode(messageLength);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
connection.connect();
try (OutputStream messageOutputStream = connection.getOutputStream()) {
messageOutputStream.write(messageBody);
}
// Get the response
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
// Get the body as a json
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
final StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
// Parse the response as json
final JsonObject responseJson = JsonParser.parseString(response.toString()).getAsJsonObject();
if (responseJson.has("url")) {
return responseJson.get("url").getAsString();
}
return "(Failed to get URL from response)";
}
} else {
return "(Failed to upload to logs site, got: " + connection.getResponseCode() + ")";
}
} catch (Exception e) {
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to upload data to logs site", e);
}
return "(Failed to upload to logs site)";
}
@NotNull
private String getWebContentField() {
return "content=" + URLEncoder.encode(toString(), StandardCharsets.UTF_8);
}
/**
* Dump the {@link UserDataSnapshot} to a file and return the file name
*
* @return the relative path of the file the data was dumped to
*/
@NotNull
public String toFile() throws IOException {
final File filePath = getFilePath();
// Write the data from #getString to the file using a writer
try (final FileWriter writer = new FileWriter(filePath, StandardCharsets.UTF_8, false)) {
writer.write(toString());
} catch (IOException e) {
throw new IOException("Failed to write data to file", e);
}
return "~/plugins/HuskSync/dumps/" + filePath.getName();
}
/**
* Get the file path to dump the data to
*
* @return the file path
* @throws IOException if the prerequisite dumps parent folder could not be created
*/
@NotNull
private File getFilePath() throws IOException {
return new File(getDumpsFolder(), getFileName());
}
/**
* Get the folder to dump the data to and create it if it does not exist
*
* @return the dumps folder
* @throws IOException if the folder could not be created
*/
@NotNull
private File getDumpsFolder() throws IOException {
final File dumpsFolder = new File(plugin.getDataFolder(), "dumps");
if (!dumpsFolder.exists()) {
if (!dumpsFolder.mkdirs()) {
throw new IOException("Failed to create user data dumps folder");
}
}
return dumpsFolder;
}
/**
* Get the name of the file to dump the data snapshot to
*
* @return the file name
*/
@NotNull
private String getFileName() {
return new StringJoiner("_")
.add(user.username)
.add(new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(dataSnapshot.versionTimestamp()))
.add(dataSnapshot.cause().name().toLowerCase())
.add(dataSnapshot.versionUUID().toString().split("-")[0])
+ ".json";
}
}