diff --git a/src/main/java/com/gmail/nossr50/database/DatabaseManager.java b/src/main/java/com/gmail/nossr50/database/DatabaseManager.java index cbe63d41e..261d0a412 100644 --- a/src/main/java/com/gmail/nossr50/database/DatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/DatabaseManager.java @@ -110,6 +110,8 @@ public interface DatabaseManager { */ public void convertUsers(DatabaseManager destination); + public boolean saveUserUUID(String userName, UUID uuid); + /** * Retrieve the type of database in use. Custom databases should return CUSTOM. * diff --git a/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java index f185214a4..25885ad93 100644 --- a/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/FlatfileDatabaseManager.java @@ -471,6 +471,46 @@ public final class FlatfileDatabaseManager implements DatabaseManager { } } + public boolean saveUserUUID(String userName, UUID uuid) { + boolean worked = false; + + BufferedReader in = null; + FileWriter out = null; + String usersFilePath = mcMMO.getUsersFilePath(); + + synchronized (fileWritingLock) { + try { + in = new BufferedReader(new FileReader(usersFilePath)); + StringBuilder writer = new StringBuilder(); + String line; + + while ((line = in.readLine()) != null) { + // Write out the same file but when we get to the player we want to remove, we skip his line. + if (!worked && line.split(":")[0].equalsIgnoreCase(userName)) { + mcMMO.p.getLogger().info("User found, updating UUID..."); + line.split(":")[41] = uuid.toString(); + worked = true; + } + + writer.append(line).append("\r\n"); + } + + out = new FileWriter(usersFilePath); // Write out the new file + out.write(writer.toString()); + } + catch (Exception e) { + mcMMO.p.getLogger().severe("Exception while reading " + usersFilePath + " (Are you sure you formatted it correctly?)" + e.toString()); + } + finally { + tryClose(in); + tryClose(out); + } + } + + System.out.println("Saving " + userName + " | uuid = " + uuid.toString()); + return worked; + } + public List getStoredUsers() { ArrayList users = new ArrayList(); BufferedReader in = null; diff --git a/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java index 8ecd42f4c..1c7302135 100644 --- a/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java @@ -491,6 +491,10 @@ public final class SQLDatabaseManager implements DatabaseManager { } + public boolean saveUserUUID(String userName, UUID uuid) { + return false; + } + /** * Check connection status and re-establish if dead or stale. *

diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index d4ea83366..e726e475d 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -36,6 +36,7 @@ import com.gmail.nossr50.runnables.CheckDateTask; import com.gmail.nossr50.runnables.SaveTimerTask; import com.gmail.nossr50.runnables.UpdaterResultAsyncTask; import com.gmail.nossr50.runnables.backups.CleanBackupsTask; +import com.gmail.nossr50.runnables.database.UUIDUpdateAsyncTask; import com.gmail.nossr50.runnables.database.UserPurgeTask; import com.gmail.nossr50.runnables.party.PartyAutoKickTask; import com.gmail.nossr50.runnables.player.PowerLevelUpdatingTask; @@ -458,6 +459,9 @@ public class mcMMO extends JavaPlugin { long saveIntervalTicks = Config.getInstance().getSaveInterval() * 1200; new SaveTimerTask().runTaskTimer(this, saveIntervalTicks, saveIntervalTicks); + // Slowly update every entry in the database with UUIDs + new UUIDUpdateAsyncTask(this).runTaskTimerAsynchronously(this, 5 * Misc.TICK_CONVERSION_FACTOR, 30 * Misc.TICK_CONVERSION_FACTOR); + // Cleanup the backups folder new CleanBackupsTask().runTaskAsynchronously(mcMMO.p); diff --git a/src/main/java/com/gmail/nossr50/runnables/database/UUIDFetcherRunnable.java b/src/main/java/com/gmail/nossr50/runnables/database/UUIDFetcherRunnable.java new file mode 100644 index 000000000..485e0f855 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/database/UUIDFetcherRunnable.java @@ -0,0 +1,51 @@ +package com.gmail.nossr50.runnables.database; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + +import org.bukkit.scheduler.BukkitRunnable; + +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.util.uuid.UUIDFetcher; + +public class UUIDFetcherRunnable extends BukkitRunnable { + private List names; + + public UUIDFetcherRunnable(List names) { + this.names = names; + } + + public UUIDFetcherRunnable(String name) { + this.names = new ArrayList(); + this.names.add(name); + } + + @Override + public void run() { + try { + Map returns = new UUIDFetcher(this.names).call(); + new CacheReturnedNames(returns).runTask(mcMMO.p); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private class CacheReturnedNames extends BukkitRunnable { + private Map returns; + + public CacheReturnedNames(Map returns) { + this.returns = returns; + } + + @Override + public void run() { + for (Entry entry : this.returns.entrySet()) { + mcMMO.getDatabaseManager().saveUserUUID(entry.getKey(), entry.getValue()); + } + } + } +} diff --git a/src/main/java/com/gmail/nossr50/runnables/database/UUIDUpdateAsyncTask.java b/src/main/java/com/gmail/nossr50/runnables/database/UUIDUpdateAsyncTask.java new file mode 100644 index 000000000..ac89ecd53 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/database/UUIDUpdateAsyncTask.java @@ -0,0 +1,76 @@ +package com.gmail.nossr50.runnables.database; + +import java.util.List; + +import org.bukkit.scheduler.BukkitRunnable; + +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.config.HiddenConfig; +import com.gmail.nossr50.database.DatabaseManager; +import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.util.Misc; + +public class UUIDUpdateAsyncTask extends BukkitRunnable { + private mcMMO plugin; + private static final int MAX_LOOKUP = 5; + + private DatabaseManager databaseManager; + private List userNames; + private int size; + int checkedUsers; + long startMillis; + + public UUIDUpdateAsyncTask(mcMMO plugin) { + this.plugin = plugin; + + this.databaseManager = mcMMO.getDatabaseManager(); + this.userNames = databaseManager.getStoredUsers(); + this.size = userNames.size(); + + this.checkedUsers = 0; + this.startMillis = System.currentTimeMillis(); + + plugin.getLogger().info("Starting to check and update UUIDs, total amount of users: " + size); + } + + @Override + public void run() { + if (!conversionNeeded()) { + plugin.debug("No need to update database with UUIDs"); + this.cancel(); + return; + } + + List userNamesSection; + + if (size > MAX_LOOKUP) { + userNamesSection = userNames.subList(size - MAX_LOOKUP, size); + size -= MAX_LOOKUP; + } + else { + userNamesSection = userNames.subList(0, size); + size = 0; + this.cancel(); + } + + for (String userName : userNamesSection) { + PlayerProfile profile = databaseManager.loadPlayerProfile(userName, false); + + checkedUsers++; + + if (profile == null || !profile.isLoaded() || profile.getUniqueId() != null) { + continue; + } + + new UUIDFetcherRunnable(userName).runTaskAsynchronously(mcMMO.p); + } + + Misc.printProgress(checkedUsers, DatabaseManager.progressInterval, startMillis); + } + + private boolean conversionNeeded() { + plugin.debug("Checking if conversion is needed..."); + + return true; + } +} diff --git a/src/main/java/com/gmail/nossr50/util/uuid/UUIDFetcher.java b/src/main/java/com/gmail/nossr50/util/uuid/UUIDFetcher.java new file mode 100644 index 000000000..6fd43dcb7 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/uuid/UUIDFetcher.java @@ -0,0 +1,87 @@ +package com.gmail.nossr50.util.uuid; + +import java.io.DataOutputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; + +import com.google.common.collect.ImmutableList; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.json.simple.parser.JSONParser; + +public class UUIDFetcher implements Callable> { + private static final int MAX_SEARCH = 100; + private static final String PROFILE_URL = "https://api.mojang.com/profiles/page/"; + private static final String AGENT = "minecraft"; + private final JSONParser jsonParser = new JSONParser(); + private final List names; + + public UUIDFetcher(List names) { + this.names = ImmutableList.copyOf(names); + } + + public Map call() throws Exception { + Map uuidMap = new HashMap(); + String body = buildBody(names); + for (int i = 1; i < MAX_SEARCH; i++) { + HttpURLConnection connection = createConnection(i); + writeBody(connection, body); + JSONObject jsonObject = (JSONObject) jsonParser.parse(new InputStreamReader(connection.getInputStream())); + JSONArray array = (JSONArray) jsonObject.get("profiles"); + Number count = (Number) jsonObject.get("size"); + + if (count.intValue() == 0) { + break; + } + + for (Object profile : array) { + JSONObject jsonProfile = (JSONObject) profile; + String id = (String) jsonProfile.get("id"); + String name = (String) jsonProfile.get("name"); + UUID uuid = UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32)); + uuidMap.put(name, uuid); + } + } + return uuidMap; + } + + private static void writeBody(HttpURLConnection connection, String body) throws Exception { + DataOutputStream writer = new DataOutputStream(connection.getOutputStream()); + writer.write(body.getBytes()); + writer.flush(); + writer.close(); + } + + private static HttpURLConnection createConnection(int page) throws Exception { + URL url = new URL(PROFILE_URL + page); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setUseCaches(false); + connection.setDoInput(true); + connection.setDoOutput(true); + return connection; + } + + @SuppressWarnings("unchecked") + private static String buildBody(List names) { + List lookups = new ArrayList(); + + for (String name : names) { + JSONObject obj = new JSONObject(); + obj.put("name", name); + obj.put("agent", AGENT); + lookups.add(obj); + } + + return JSONValue.toJSONString(lookups); + } +}