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 executor) {
 | |
| 		this.executor = executor;
 | |
| 	}
 | |
| 
 | |
| 	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;
 | |
| 		}
 | |
| 	}
 | |
| }
 |