363 lines
11 KiB
Java
363 lines
11 KiB
Java
package net.minecraft.server.players;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Maps;
|
|
import com.google.common.io.Files;
|
|
import com.google.gson.Gson;
|
|
import com.google.gson.GsonBuilder;
|
|
import com.google.gson.JsonArray;
|
|
import com.google.gson.JsonElement;
|
|
import com.google.gson.JsonObject;
|
|
import com.google.gson.JsonParseException;
|
|
import com.mojang.authlib.GameProfile;
|
|
import com.mojang.authlib.GameProfileRepository;
|
|
import com.mojang.logging.LogUtils;
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.Reader;
|
|
import java.io.Writer;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.text.DateFormat;
|
|
import java.text.ParseException;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.Calendar;
|
|
import java.util.Comparator;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
import java.util.stream.Stream;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.core.UUIDUtil;
|
|
import net.minecraft.util.StringUtil;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public class GameProfileCache {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private static final int GAMEPROFILES_MRU_LIMIT = 1000;
|
|
private static final int GAMEPROFILES_EXPIRATION_MONTHS = 1;
|
|
private static boolean usesAuthentication;
|
|
/**
|
|
* A map between player usernames and
|
|
*/
|
|
private final Map<String, GameProfileCache.GameProfileInfo> profilesByName = Maps.<String, GameProfileCache.GameProfileInfo>newConcurrentMap();
|
|
/**
|
|
* A map between and
|
|
*/
|
|
private final Map<UUID, GameProfileCache.GameProfileInfo> profilesByUUID = Maps.<UUID, GameProfileCache.GameProfileInfo>newConcurrentMap();
|
|
private final Map<String, CompletableFuture<Optional<GameProfile>>> requests = Maps.<String, CompletableFuture<Optional<GameProfile>>>newConcurrentMap();
|
|
private final GameProfileRepository profileRepository;
|
|
private final Gson gson = new GsonBuilder().create();
|
|
private final File file;
|
|
private final AtomicLong operationCount = new AtomicLong();
|
|
@Nullable
|
|
private Executor executor;
|
|
|
|
public GameProfileCache(GameProfileRepository profileRepository, File file) {
|
|
this.profileRepository = profileRepository;
|
|
this.file = file;
|
|
Lists.reverse(this.load()).forEach(this::safeAdd);
|
|
}
|
|
|
|
private void safeAdd(GameProfileCache.GameProfileInfo profile) {
|
|
GameProfile gameProfile = profile.getProfile();
|
|
profile.setLastAccess(this.getNextOperation());
|
|
this.profilesByName.put(gameProfile.getName().toLowerCase(Locale.ROOT), profile);
|
|
this.profilesByUUID.put(gameProfile.getId(), profile);
|
|
}
|
|
|
|
private static Optional<GameProfile> lookupGameProfile(GameProfileRepository profileRepo, String name) {
|
|
if (!StringUtil.isValidPlayerName(name)) {
|
|
return createUnknownProfile(name);
|
|
} else {
|
|
Optional<GameProfile> optional = profileRepo.findProfileByName(name);
|
|
return optional.isEmpty() ? createUnknownProfile(name) : optional;
|
|
}
|
|
}
|
|
|
|
private static Optional<GameProfile> createUnknownProfile(String profileName) {
|
|
return usesAuthentication() ? Optional.empty() : Optional.of(UUIDUtil.createOfflineProfile(profileName));
|
|
}
|
|
|
|
public static void setUsesAuthentication(boolean onlineMode) {
|
|
usesAuthentication = onlineMode;
|
|
}
|
|
|
|
private static boolean usesAuthentication() {
|
|
return usesAuthentication;
|
|
}
|
|
|
|
/**
|
|
* Add an entry to this cache
|
|
*/
|
|
public void add(GameProfile gameProfile) {
|
|
Calendar calendar = Calendar.getInstance();
|
|
calendar.setTime(new Date());
|
|
calendar.add(2, 1);
|
|
Date date = calendar.getTime();
|
|
GameProfileCache.GameProfileInfo gameProfileInfo = new GameProfileCache.GameProfileInfo(gameProfile, date);
|
|
this.safeAdd(gameProfileInfo);
|
|
this.save();
|
|
}
|
|
|
|
private long getNextOperation() {
|
|
return this.operationCount.incrementAndGet();
|
|
}
|
|
|
|
/**
|
|
* Get a player's GameProfile given their username. Mojang's servers will be contacted if the entry is not cached locally.
|
|
*/
|
|
public Optional<GameProfile> get(String name) {
|
|
String string = name.toLowerCase(Locale.ROOT);
|
|
GameProfileCache.GameProfileInfo gameProfileInfo = (GameProfileCache.GameProfileInfo)this.profilesByName.get(string);
|
|
boolean bl = false;
|
|
if (gameProfileInfo != null && new Date().getTime() >= gameProfileInfo.expirationDate.getTime()) {
|
|
this.profilesByUUID.remove(gameProfileInfo.getProfile().getId());
|
|
this.profilesByName.remove(gameProfileInfo.getProfile().getName().toLowerCase(Locale.ROOT));
|
|
bl = true;
|
|
gameProfileInfo = null;
|
|
}
|
|
|
|
Optional<GameProfile> optional;
|
|
if (gameProfileInfo != null) {
|
|
gameProfileInfo.setLastAccess(this.getNextOperation());
|
|
optional = Optional.of(gameProfileInfo.getProfile());
|
|
} else {
|
|
optional = lookupGameProfile(this.profileRepository, string);
|
|
if (optional.isPresent()) {
|
|
this.add((GameProfile)optional.get());
|
|
bl = false;
|
|
}
|
|
}
|
|
|
|
if (bl) {
|
|
this.save();
|
|
}
|
|
|
|
return optional;
|
|
}
|
|
|
|
public CompletableFuture<Optional<GameProfile>> getAsync(String name) {
|
|
if (this.executor == null) {
|
|
throw new IllegalStateException("No executor");
|
|
} else {
|
|
CompletableFuture<Optional<GameProfile>> completableFuture = (CompletableFuture<Optional<GameProfile>>)this.requests.get(name);
|
|
if (completableFuture != null) {
|
|
return completableFuture;
|
|
} else {
|
|
CompletableFuture<Optional<GameProfile>> completableFuture2 = CompletableFuture.supplyAsync(
|
|
() -> this.get(name), Util.backgroundExecutor().forName("getProfile")
|
|
)
|
|
.whenCompleteAsync((optional, throwable) -> this.requests.remove(name), this.executor);
|
|
this.requests.put(name, completableFuture2);
|
|
return completableFuture2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param uuid Get a player's {@link GameProfile} given their UUID
|
|
*/
|
|
public Optional<GameProfile> get(UUID uuid) {
|
|
GameProfileCache.GameProfileInfo gameProfileInfo = (GameProfileCache.GameProfileInfo)this.profilesByUUID.get(uuid);
|
|
if (gameProfileInfo == null) {
|
|
return Optional.empty();
|
|
} else {
|
|
gameProfileInfo.setLastAccess(this.getNextOperation());
|
|
return Optional.of(gameProfileInfo.getProfile());
|
|
}
|
|
}
|
|
|
|
public void setExecutor(Executor exectutor) {
|
|
this.executor = exectutor;
|
|
}
|
|
|
|
public void clearExecutor() {
|
|
this.executor = null;
|
|
}
|
|
|
|
private static DateFormat createDateFormat() {
|
|
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT);
|
|
}
|
|
|
|
public List<GameProfileCache.GameProfileInfo> load() {
|
|
List<GameProfileCache.GameProfileInfo> list = Lists.<GameProfileCache.GameProfileInfo>newArrayList();
|
|
|
|
try {
|
|
Reader reader = Files.newReader(this.file, StandardCharsets.UTF_8);
|
|
|
|
Object var9;
|
|
label60: {
|
|
try {
|
|
JsonArray jsonArray = this.gson.fromJson(reader, JsonArray.class);
|
|
if (jsonArray == null) {
|
|
var9 = list;
|
|
break label60;
|
|
}
|
|
|
|
DateFormat dateFormat = createDateFormat();
|
|
jsonArray.forEach(jsonElement -> readGameProfile(jsonElement, dateFormat).ifPresent(list::add));
|
|
} catch (Throwable var6) {
|
|
if (reader != null) {
|
|
try {
|
|
reader.close();
|
|
} catch (Throwable var5) {
|
|
var6.addSuppressed(var5);
|
|
}
|
|
}
|
|
|
|
throw var6;
|
|
}
|
|
|
|
if (reader != null) {
|
|
reader.close();
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
if (reader != null) {
|
|
reader.close();
|
|
}
|
|
|
|
return (List<GameProfileCache.GameProfileInfo>)var9;
|
|
} catch (FileNotFoundException var7) {
|
|
} catch (JsonParseException | IOException var8) {
|
|
LOGGER.warn("Failed to load profile cache {}", this.file, var8);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Save the cached profiles to disk
|
|
*/
|
|
public void save() {
|
|
JsonArray jsonArray = new JsonArray();
|
|
DateFormat dateFormat = createDateFormat();
|
|
this.getTopMRUProfiles(1000).forEach(gameProfileInfo -> jsonArray.add(writeGameProfile(gameProfileInfo, dateFormat)));
|
|
String string = this.gson.toJson((JsonElement)jsonArray);
|
|
|
|
try {
|
|
Writer writer = Files.newWriter(this.file, StandardCharsets.UTF_8);
|
|
|
|
try {
|
|
writer.write(string);
|
|
} catch (Throwable var8) {
|
|
if (writer != null) {
|
|
try {
|
|
writer.close();
|
|
} catch (Throwable var7) {
|
|
var8.addSuppressed(var7);
|
|
}
|
|
}
|
|
|
|
throw var8;
|
|
}
|
|
|
|
if (writer != null) {
|
|
writer.close();
|
|
}
|
|
} catch (IOException var9) {
|
|
}
|
|
}
|
|
|
|
private Stream<GameProfileCache.GameProfileInfo> getTopMRUProfiles(int limit) {
|
|
return ImmutableList.copyOf(this.profilesByUUID.values())
|
|
.stream()
|
|
.sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed())
|
|
.limit(limit);
|
|
}
|
|
|
|
private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo profileInfo, DateFormat dateFormat) {
|
|
JsonObject jsonObject = new JsonObject();
|
|
jsonObject.addProperty("name", profileInfo.getProfile().getName());
|
|
jsonObject.addProperty("uuid", profileInfo.getProfile().getId().toString());
|
|
jsonObject.addProperty("expiresOn", dateFormat.format(profileInfo.getExpirationDate()));
|
|
return jsonObject;
|
|
}
|
|
|
|
private static Optional<GameProfileCache.GameProfileInfo> readGameProfile(JsonElement json, DateFormat dateFormat) {
|
|
if (json.isJsonObject()) {
|
|
JsonObject jsonObject = json.getAsJsonObject();
|
|
JsonElement jsonElement = jsonObject.get("name");
|
|
JsonElement jsonElement2 = jsonObject.get("uuid");
|
|
JsonElement jsonElement3 = jsonObject.get("expiresOn");
|
|
if (jsonElement != null && jsonElement2 != null) {
|
|
String string = jsonElement2.getAsString();
|
|
String string2 = jsonElement.getAsString();
|
|
Date date = null;
|
|
if (jsonElement3 != null) {
|
|
try {
|
|
date = dateFormat.parse(jsonElement3.getAsString());
|
|
} catch (ParseException var12) {
|
|
}
|
|
}
|
|
|
|
if (string2 != null && string != null && date != null) {
|
|
UUID uUID;
|
|
try {
|
|
uUID = UUID.fromString(string);
|
|
} catch (Throwable var11) {
|
|
return Optional.empty();
|
|
}
|
|
|
|
return Optional.of(new GameProfileCache.GameProfileInfo(new GameProfile(uUID, string2), date));
|
|
} else {
|
|
return Optional.empty();
|
|
}
|
|
} else {
|
|
return Optional.empty();
|
|
}
|
|
} else {
|
|
return Optional.empty();
|
|
}
|
|
}
|
|
|
|
static class GameProfileInfo {
|
|
/**
|
|
* The player's GameProfile
|
|
*/
|
|
private final GameProfile profile;
|
|
/**
|
|
* The date that this entry will expire
|
|
*/
|
|
final Date expirationDate;
|
|
private volatile long lastAccess;
|
|
|
|
GameProfileInfo(GameProfile profile, Date expirationDate) {
|
|
this.profile = profile;
|
|
this.expirationDate = expirationDate;
|
|
}
|
|
|
|
/**
|
|
* Get the player's GameProfile
|
|
*/
|
|
public GameProfile getProfile() {
|
|
return this.profile;
|
|
}
|
|
|
|
/**
|
|
* Get the date that this entry will expire
|
|
*/
|
|
public Date getExpirationDate() {
|
|
return this.expirationDate;
|
|
}
|
|
|
|
public void setLastAccess(long lastAccess) {
|
|
this.lastAccess = lastAccess;
|
|
}
|
|
|
|
public long getLastAccess() {
|
|
return this.lastAccess;
|
|
}
|
|
}
|
|
}
|