minecraft-src/net/minecraft/server/players/GameProfileCache.java
2025-07-04 03:45:38 +03:00

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;
}
}
}