Fixed migration for players who changed names before migrating.

* NEW: UUIDFetcher can now fetch uuids at a specific timestamp.
* BUG: Old map files are not loaded twice anymore.
* OPT: Reduced minimum time between requests.
This commit is contained in:
Prokopyl 2015-04-10 02:22:47 +02:00
parent 90dd9eb24d
commit 6725c15002
2 changed files with 158 additions and 49 deletions

View File

@ -23,6 +23,8 @@ import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -34,20 +36,72 @@ import org.json.simple.parser.ParseException;
abstract public class UUIDFetcher abstract public class UUIDFetcher
{ {
static private final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft"; //The URI of the name->UUID API from Mojang /**
static private final JSONParser jsonParser = new JSONParser(); * The maximal amount of usernames to send to mojang per request
* This allows not to overload mojang's service with too many usernames at a time
static public Map<String, UUID> fetch(List<String> names) throws IOException */
static private final int MOJANG_USERNAMES_PER_REQUEST = 100;
/**
* The maximal amount of requests to send to Mojang
* The time limit for this amount is MOJANG_MAX_REQUESTS_TIME
* Read : You can only send MOJANG_MAX_REQUESTS in MOJANG_MAX_REQUESTS_TIME seconds
*/
static private final int MOJANG_MAX_REQUESTS = 600;
/**
* The timeframe for the Mojang request limit (in seconds)
*/
static private final int MOJANG_MAX_REQUESTS_TIME = 600;
/**
* The minimum time between two requests to mojang (in milliseconds)
*/
static private final int TIME_BETWEEN_REQUESTS = 200;
/**
* The (approximative) timestamp of the date when Mojang name changing feature
* was announced to be released
*/
static private final int NAME_CHANGE_TIMESTAMP = 1420844400;
static private final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
static private final String TIMED_PROFILE_URL = "https://api.mojang.com/users/profiles/minecraft/";
static public Map<String, UUID> fetch(List<String> names) throws IOException, InterruptedException
{
return fetch(names, MOJANG_USERNAMES_PER_REQUEST);
}
static public Map<String, UUID> fetch(List<String> names, int limitByRequest) throws IOException, InterruptedException
{
Map<String, UUID> UUIDs = new HashMap<String, UUID>();
int requests = (names.size() / limitByRequest) + 1;
List<String> tempNames;
Map<String, UUID> tempUUIDs;
for(int i = 0; i < requests; i++)
{
tempNames = names.subList(limitByRequest * i, Math.min((limitByRequest * (i+1)) - 1, names.size()));
tempUUIDs = rawFetch(tempNames);
UUIDs.putAll(tempUUIDs);
Thread.sleep(TIME_BETWEEN_REQUESTS);
}
return UUIDs;
}
static private Map<String, UUID> rawFetch(List<String> names) throws IOException
{ {
Map<String, UUID> uuidMap = new HashMap<String, UUID>(); Map<String, UUID> uuidMap = new HashMap<String, UUID>();
HttpURLConnection connection = createConnection(); HttpURLConnection connection = getPOSTConnection(PROFILE_URL);
writeBody(connection, names); writeBody(connection, names);
JSONArray array; JSONArray array;
try try
{ {
array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream())); array = (JSONArray) readResponse(connection);
} }
catch(ParseException ex) catch(ParseException ex)
{ {
@ -62,33 +116,63 @@ abstract public class UUIDFetcher
uuidMap.put(name, fromMojangUUID(id)); uuidMap.put(name, fromMojangUUID(id));
} }
return uuidMap; return uuidMap;
} }
static public Map<String, UUID> fetch(List<String> names, int limitByRequest) throws IOException, InterruptedException static public void fetchRemaining(Collection<String> names, Map<String, UUID> uuids) throws IOException, InterruptedException
{ {
Map<String, UUID> UUIDs = new HashMap<String, UUID>(); ArrayList<String> remainingNames = new ArrayList<>();
int requests = (names.size() / limitByRequest) + 1;
List<String> tempNames; for(String name : names)
Map<String, UUID> tempUUIDs;
for(int i = 0; i < requests; i++)
{ {
tempNames = names.subList(limitByRequest * i, Math.min((limitByRequest * (i+1)) - 1, names.size())); if(!uuids.containsKey(name)) remainingNames.add(name);
tempUUIDs = fetch(tempNames); }
UUIDs.putAll(tempUUIDs);
Thread.sleep(400); int timeBetweenRequests;
if(remainingNames.size() > MOJANG_MAX_REQUESTS)
{
timeBetweenRequests = (MOJANG_MAX_REQUESTS / MOJANG_MAX_REQUESTS_TIME) * 1000;
}
else
{
timeBetweenRequests = TIME_BETWEEN_REQUESTS;
}
User user;
for(String name : remainingNames)
{
user = fetchOriginalUUID(name);
uuids.put(user.name, user.uuid);
Thread.sleep(timeBetweenRequests);
} }
return UUIDs;
} }
private static HttpURLConnection createConnection() throws IOException static private User fetchOriginalUUID(String name) throws IOException
{ {
URL url = new URL(PROFILE_URL); HttpURLConnection connection = getGETConnection(TIMED_PROFILE_URL + name + "?at=" + NAME_CHANGE_TIMESTAMP);
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); sendRequest(connection);
JSONObject object;
try
{
object = (JSONObject) readResponse(connection);
}
catch(ParseException ex)
{
throw new IOException("Invalid response from server, unable to parse received JSON : " + ex.toString());
}
User user = new User();
user.name = (String) object.get("name");
user.uuid = fromMojangUUID((String)object.get("id"));
return user;
}
static private HttpURLConnection getPOSTConnection(String url) throws IOException
{
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("POST"); connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json"); connection.setRequestProperty("Content-Type", "application/json");
connection.setUseCaches(false); connection.setUseCaches(false);
@ -96,7 +180,22 @@ abstract public class UUIDFetcher
connection.setDoOutput(true); connection.setDoOutput(true);
return connection; return connection;
} }
static private HttpURLConnection getGETConnection(String url) throws IOException
{
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("GET");
connection.setUseCaches(false);
connection.setDoInput(true);
return connection;
}
static private void sendRequest(HttpURLConnection connection) throws IOException
{
OutputStream stream = connection.getOutputStream();
stream.flush();
stream.close();
}
private static void writeBody(HttpURLConnection connection, List<String> names) throws IOException private static void writeBody(HttpURLConnection connection, List<String> names) throws IOException
{ {
@ -106,7 +205,11 @@ abstract public class UUIDFetcher
stream.flush(); stream.flush();
stream.close(); stream.close();
} }
private static Object readResponse(HttpURLConnection connection) throws IOException, ParseException
{
return new JSONParser().parse(new InputStreamReader(connection.getInputStream()));
}
private static UUID fromMojangUUID(String id) //Mojang sends string UUIDs without dashes ... private static UUID fromMojangUUID(String id) //Mojang sends string UUIDs without dashes ...
{ {
@ -114,6 +217,12 @@ abstract public class UUIDFetcher
id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" +
id.substring(20, 32)); id.substring(20, 32));
} }
static private class User
{
public String name;
public UUID uuid;
}
} }

View File

@ -71,12 +71,6 @@ public class V3Migrator
*/ */
static private final String BACKUPS_POSTV3_DIRECTORY_NAME = "backups_post-v3"; static private final String BACKUPS_POSTV3_DIRECTORY_NAME = "backups_post-v3";
/**
* The maximal amount of usernames to send to mojang per request
* This allows not to overload mojang's service with too many usernames at a time
*/
static private final int MOJANG_USERNAMES_PER_REQUEST = 100;
/** /**
* Returns the former images directory of a given plugin * Returns the former images directory of a given plugin
* @param plugin The plugin. * @param plugin The plugin.
@ -167,9 +161,8 @@ public class V3Migrator
if(checkForExistingBackups()) return; if(checkForExistingBackups()) return;
if(!loadOldFiles()) return; if(!loadOldFiles()) return;
backupMapData(); backupMapData();
loadOldFiles();
fetchUUIDs(); fetchUUIDs();
if(!checkMissingUUIDs()) return; if(!fetchMissingUUIDs()) return;
} }
catch(Exception ex) catch(Exception ex)
{ {
@ -341,7 +334,7 @@ public class V3Migrator
logInfo("Fetching UUIDs from Mojang ..."); logInfo("Fetching UUIDs from Mojang ...");
try try
{ {
usersUUIDs = UUIDFetcher.fetch(new ArrayList<String>(userNamesToFetch), MOJANG_USERNAMES_PER_REQUEST); usersUUIDs = UUIDFetcher.fetch(new ArrayList<String>(userNamesToFetch));
} }
catch(IOException ex) catch(IOException ex)
{ {
@ -357,14 +350,30 @@ public class V3Migrator
} }
/** /**
* Check if any UUID has been retreived. * Fetches the UUIDs that could not be retreived via Mojang's standard API
* @return true if at least one UUID has been retreived, false otherwise * @return true if at least one UUID has been retreived, false otherwise
*/ */
private boolean checkMissingUUIDs() private boolean fetchMissingUUIDs() throws IOException, InterruptedException
{ {
if(usersUUIDs.size() == userNamesToFetch.size()) return true; if(usersUUIDs.size() == userNamesToFetch.size()) return true;
logInfo("Mojang did not find UUIDs for all the registered players."); int remainingUsersCount = userNamesToFetch.size() - usersUUIDs.size();
logInfo("This means some of the users do not actually exist, or they have changed names before migrating."); logInfo("Mojang did not find UUIDs for "+remainingUsersCount+" players.");
logInfo("The Mojang servers limit requests rate at one per second, this may take some time...");
try
{
UUIDFetcher.fetchRemaining(userNamesToFetch, usersUUIDs);
}
catch(IOException ex)
{
logError("An error occured while fetching the UUIDs from Mojang", ex);
throw ex;
}
catch(InterruptedException ex)
{
logError("The migration worker has been interrupted", ex);
throw ex;
}
if(usersUUIDs.size() <= 0) if(usersUUIDs.size() <= 0)
{ {
@ -373,15 +382,6 @@ public class V3Migrator
return false; return false;
} }
String missingUsersList = "";
for(String user : userNamesToFetch)
{
if(!usersUUIDs.containsKey(user)) missingUsersList += user + ",";
}
missingUsersList = missingUsersList.substring(0, missingUsersList.length());
logInfo("Here are the missing players : " + missingUsersList);
return true; return true;
} }