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.authlib.ProfileLookupCallback; 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.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import javax.annotation.Nullable; import net.minecraft.Util; import net.minecraft.core.UUIDUtil; import net.minecraft.util.StringUtil; 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; private final Map profilesByName = Maps.newConcurrentMap(); private final Map profilesByUUID = Maps.newConcurrentMap(); private final Map>> requests = Maps.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 p_10974_, File p_10975_) { this.profileRepository = p_10974_; this.file = p_10975_; Lists.reverse(this.load()).forEach(this::safeAdd); } private void safeAdd(GameProfileCache.GameProfileInfo p_10980_) { GameProfile gameprofile = p_10980_.getProfile(); p_10980_.setLastAccess(this.getNextOperation()); this.profilesByName.put(gameprofile.getName().toLowerCase(Locale.ROOT), p_10980_); this.profilesByUUID.put(gameprofile.getId(), p_10980_); } private static Optional lookupGameProfile(GameProfileRepository p_10994_, String p_10995_) { if (!StringUtil.isValidPlayerName(p_10995_)) { return createUnknownProfile(p_10995_); } else { final AtomicReference atomicreference = new AtomicReference<>(); ProfileLookupCallback profilelookupcallback = new ProfileLookupCallback() { @Override public void onProfileLookupSucceeded(GameProfile p_11017_) { atomicreference.set(p_11017_); } @Override public void onProfileLookupFailed(String p_300244_, Exception p_11015_) { atomicreference.set(null); } }; p_10994_.findProfilesByNames(new String[]{p_10995_}, profilelookupcallback); GameProfile gameprofile = atomicreference.get(); return gameprofile != null ? Optional.of(gameprofile) : createUnknownProfile(p_10995_); } } private static Optional createUnknownProfile(String p_311687_) { return usesAuthentication() ? Optional.empty() : Optional.of(UUIDUtil.createOfflineProfile(p_311687_)); } public static void setUsesAuthentication(boolean p_11005_) { usesAuthentication = p_11005_; } private static boolean usesAuthentication() { return usesAuthentication; } public void add(GameProfile p_10992_) { Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); calendar.add(2, 1); Date date = calendar.getTime(); GameProfileCache.GameProfileInfo gameprofilecache$gameprofileinfo = new GameProfileCache.GameProfileInfo(p_10992_, date); this.safeAdd(gameprofilecache$gameprofileinfo); this.save(); } private long getNextOperation() { return this.operationCount.incrementAndGet(); } public Optional get(String p_10997_) { String s = p_10997_.toLowerCase(Locale.ROOT); GameProfileCache.GameProfileInfo gameprofilecache$gameprofileinfo = this.profilesByName.get(s); boolean flag = false; if (gameprofilecache$gameprofileinfo != null && new Date().getTime() >= gameprofilecache$gameprofileinfo.expirationDate.getTime()) { this.profilesByUUID.remove(gameprofilecache$gameprofileinfo.getProfile().getId()); this.profilesByName.remove(gameprofilecache$gameprofileinfo.getProfile().getName().toLowerCase(Locale.ROOT)); flag = true; gameprofilecache$gameprofileinfo = null; } Optional optional; if (gameprofilecache$gameprofileinfo != null) { gameprofilecache$gameprofileinfo.setLastAccess(this.getNextOperation()); optional = Optional.of(gameprofilecache$gameprofileinfo.getProfile()); } else { optional = lookupGameProfile(this.profileRepository, s); if (optional.isPresent()) { this.add(optional.get()); flag = false; } } if (flag) { this.save(); } return optional; } public CompletableFuture> getAsync(String p_143968_) { if (this.executor == null) { throw new IllegalStateException("No executor"); } else { CompletableFuture> completablefuture = this.requests.get(p_143968_); if (completablefuture != null) { return completablefuture; } else { CompletableFuture> completablefuture1 = CompletableFuture.>supplyAsync( () -> this.get(p_143968_), Util.backgroundExecutor().forName("getProfile") ) .whenCompleteAsync((p_143965_, p_143966_) -> this.requests.remove(p_143968_), this.executor); this.requests.put(p_143968_, completablefuture1); return completablefuture1; } } } public Optional get(UUID p_11003_) { GameProfileCache.GameProfileInfo gameprofilecache$gameprofileinfo = this.profilesByUUID.get(p_11003_); if (gameprofilecache$gameprofileinfo == null) { return Optional.empty(); } else { gameprofilecache$gameprofileinfo.setLastAccess(this.getNextOperation()); return Optional.of(gameprofilecache$gameprofileinfo.getProfile()); } } public void setExecutor(Executor p_143975_) { this.executor = p_143975_; } public void clearExecutor() { this.executor = null; } private static DateFormat createDateFormat() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT); } public List load() { List list = Lists.newArrayList(); try { Object object; try (Reader reader = Files.newReader(this.file, StandardCharsets.UTF_8)) { JsonArray jsonarray = this.gson.fromJson(reader, JsonArray.class); if (jsonarray != null) { DateFormat dateformat = createDateFormat(); jsonarray.forEach(p_143973_ -> readGameProfile(p_143973_, dateformat).ifPresent(list::add)); return list; } object = list; } return (List)object; } catch (FileNotFoundException filenotfoundexception) { } catch (JsonParseException | IOException ioexception) { LOGGER.warn("Failed to load profile cache {}", this.file, ioexception); } return list; } public void save() { JsonArray jsonarray = new JsonArray(); DateFormat dateformat = createDateFormat(); this.getTopMRUProfiles(1000).forEach(p_143962_ -> jsonarray.add(writeGameProfile(p_143962_, dateformat))); String s = this.gson.toJson((JsonElement)jsonarray); try (Writer writer = Files.newWriter(this.file, StandardCharsets.UTF_8)) { writer.write(s); } catch (IOException ioexception) { } } private Stream getTopMRUProfiles(int p_10978_) { return ImmutableList.copyOf(this.profilesByUUID.values()) .stream() .sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()) .limit((long)p_10978_); } private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo p_10982_, DateFormat p_10983_) { JsonObject jsonobject = new JsonObject(); jsonobject.addProperty("name", p_10982_.getProfile().getName()); jsonobject.addProperty("uuid", p_10982_.getProfile().getId().toString()); jsonobject.addProperty("expiresOn", p_10983_.format(p_10982_.getExpirationDate())); return jsonobject; } private static Optional readGameProfile(JsonElement p_10989_, DateFormat p_10990_) { if (p_10989_.isJsonObject()) { JsonObject jsonobject = p_10989_.getAsJsonObject(); JsonElement jsonelement = jsonobject.get("name"); JsonElement jsonelement1 = jsonobject.get("uuid"); JsonElement jsonelement2 = jsonobject.get("expiresOn"); if (jsonelement != null && jsonelement1 != null) { String s = jsonelement1.getAsString(); String s1 = jsonelement.getAsString(); Date date = null; if (jsonelement2 != null) { try { date = p_10990_.parse(jsonelement2.getAsString()); } catch (ParseException parseexception) { } } if (s1 != null && s != null && date != null) { UUID uuid; try { uuid = UUID.fromString(s); } catch (Throwable throwable) { return Optional.empty(); } return Optional.of(new GameProfileCache.GameProfileInfo(new GameProfile(uuid, s1), date)); } else { return Optional.empty(); } } else { return Optional.empty(); } } else { return Optional.empty(); } } static class GameProfileInfo { private final GameProfile profile; final Date expirationDate; private volatile long lastAccess; GameProfileInfo(GameProfile p_11022_, Date p_11023_) { this.profile = p_11022_; this.expirationDate = p_11023_; } public GameProfile getProfile() { return this.profile; } public Date getExpirationDate() { return this.expirationDate; } public void setLastAccess(long p_11030_) { this.lastAccess = p_11030_; } public long getLastAccess() { return this.lastAccess; } } }