package com.Acrobot.ChestShop.UUIDs; import com.Acrobot.Breeze.Utils.Encoding.Base62; import com.Acrobot.Breeze.Utils.NameUtil; import com.Acrobot.Breeze.Collection.SimpleCache; import com.Acrobot.Breeze.Utils.NumberUtil; import com.Acrobot.ChestShop.ChestShop; import com.Acrobot.ChestShop.Configuration.Properties; import com.Acrobot.ChestShop.Database.Account; import com.Acrobot.ChestShop.Database.DaoCreator; import com.Acrobot.ChestShop.Events.AccountAccessEvent; import com.Acrobot.ChestShop.Events.AccountQueryEvent; import com.Acrobot.ChestShop.Permission; import com.Acrobot.ChestShop.Signs.ChestShopSign; import com.google.common.base.Preconditions; import com.j256.ormlite.dao.Dao; import com.j256.ormlite.stmt.SelectArg; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import java.sql.SQLException; import java.util.Date; import java.util.Locale; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.logging.Level; /** * Lets you save/cache username and UUID relations * * @author Andrzej Pomirski (Acrobot) */ @SuppressWarnings("UnusedAssignment") // I deliberately set the variables to null while initializing public class NameManager implements Listener { private static Dao accounts; private static SimpleCache usernameToAccount = new SimpleCache<>(Properties.CACHE_SIZE); private static SimpleCache uuidToAccount = new SimpleCache<>(Properties.CACHE_SIZE); private static SimpleCache shortToAccount = new SimpleCache<>(Properties.CACHE_SIZE); private static SimpleCache invalidPlayers = new SimpleCache<>(Properties.CACHE_SIZE); private static Account adminAccount; private static Account serverEconomyAccount; private static int uuidVersion = -1; public static int getAccountCount() { try { return NumberUtil.toInt(accounts.queryBuilder().countOf() - 1); } catch (SQLException e) { return 0; } } /** * Get or create an account for a player * * @param player The Player to get or create the account for (only if the player has both an UUID and a name!) * @return The account info * @throws IllegalArgumentException when an invalid player object was passed */ public static Account getOrCreateAccount(OfflinePlayer player) { Preconditions.checkNotNull(player.getName(), "Name of player " + player.getUniqueId() + " is null?"); Preconditions.checkArgument(player instanceof Player || !Properties.ENSURE_CORRECT_PLAYERID || uuidVersion < 0 || player.getUniqueId().version() == uuidVersion, "Invalid OfflinePlayer! " + player.getUniqueId() + " has version " + player.getUniqueId().version() + " and not server version " + uuidVersion + ". " + "If you believe that is an error and your setup allows such UUIDs then set the ENSURE_CORRECT_PLAYERID config option to false."); return getOrCreateAccount(player.getUniqueId(), player.getName()); } /** * Get or create an account for a player * * @param id The UUID of the player to get or create the account for * @param name The name of the player to get or create the account fo * @return The account info * @throws IllegalArgumentException when id or name are null */ public static Account getOrCreateAccount(UUID id, String name) { Preconditions.checkNotNull(id, "UUID of player is null?"); Preconditions.checkNotNull(name, "Name of player " + id + " is null?"); Account account = getAccount(id); if (account == null) { account = storeUsername(new PlayerDTO(id, name)); } return account; } /** * Get account info from a UUID * * @param uuid The UUID of the player to get the account info * @return The account info or null if none was found */ public static Account getAccount(UUID uuid) { try { return uuidToAccount.get(uuid, () -> { try { Account account = accounts.queryBuilder().orderBy("lastSeen", false).where().eq("uuid", new SelectArg(uuid)).queryForFirst(); if (account != null) { account.setUuid(uuid); // HOW IS IT EVEN POSSIBLE THAT UUID IS NOT SET EVEN IF WE HAVE FOUND THE PLAYER?! shortToAccount.put(account.getShortName(), account); usernameToAccount.put(account.getName(), account); return account; } } catch (SQLException e) { ChestShop.getBukkitLogger().log(Level.WARNING, "Error while getting account for " + uuid + ":", e); } throw new Exception("Could not find account for " + uuid); }); } catch (ExecutionException ignored) { return null; } } /** * Get account info from a non-shortened username * * @param fullName The full name of the player to get the account info * @return The account info or null if none was found * @throws IllegalArgumentException if the username is empty or null */ public static Account getAccount(String fullName) { Preconditions.checkNotNull(fullName, "fullName cannot be null!"); Preconditions.checkArgument(!fullName.isEmpty(), "fullName cannot be empty!"); try { return usernameToAccount.get(fullName, () -> { try { Account account = accounts.queryBuilder().orderBy("lastSeen", false).where().eq("name", new SelectArg(fullName)).queryForFirst(); if (account != null) { account.setName(fullName); // HOW IS IT EVEN POSSIBLE THAT THE NAME IS NOT SET EVEN IF WE HAVE FOUND THE PLAYER?! shortToAccount.put(account.getShortName(), account); return account; } } catch (SQLException e) { ChestShop.getBukkitLogger().log(Level.WARNING, "Error while getting account for " + fullName + ":", e); } throw new Exception("Could not find account for " + fullName); }); } catch (ExecutionException ignored) { return null; } } @EventHandler public static void onAccountQuery(AccountQueryEvent event) { if (event.getAccount() == null) { event.setAccount(getLastAccountFromName(event.getName(), event.searchOfflinePlayers())); } } /** * Get account info from a username that might be shortened. * * @param shortName The name of the player to get the account info * @return The account info or null if none was found * @throws IllegalArgumentException if the username is empty * @deprecated Use the {@link AccountQueryEvent} instead! */ @Deprecated public static Account getAccountFromShortName(String shortName) { Preconditions.checkNotNull(shortName, "shortName cannot be null!"); Preconditions.checkArgument(!shortName.isEmpty(), "shortName cannot be empty!"); Account account = null; try { account = shortToAccount.get(shortName, () -> { try { Account a = accounts.queryBuilder().where().eq("shortName", new SelectArg(shortName)).queryForFirst(); if (a != null) { a.setShortName(shortName); // HOW IS IT EVEN POSSIBLE THAT THE NAME IS NOT SET EVEN IF WE HAVE FOUND THE PLAYER?! return a; } } catch (SQLException e) { ChestShop.getBukkitLogger().log(Level.WARNING, "Error while getting account for " + shortName + ":", e); } throw new Exception("Could not find account for " + shortName); }); } catch (ExecutionException ignored) {} return account; } /** * Get the information from the last time a player logged in that previously used the (shortened) name * * @param name The name of the player to get the last account for * @param searchOfflinePlayer Whether or not to search the offline players too * @return The last account or null if none was found * @throws IllegalArgumentException if the username is empty */ private static Account getLastAccountFromName(String name, boolean searchOfflinePlayer) { Account account = getAccountFromShortName(name); // first get the account associated with the short name if (account == null) { account = getAccount(name); } if (account == null && searchOfflinePlayer && !invalidPlayers.contains(name.toLowerCase(Locale.ROOT))) { // no account with that shortname was found, try to get an offline player with that name OfflinePlayer offlinePlayer = ChestShop.getBukkitServer().getOfflinePlayer(name); if (offlinePlayer != null && offlinePlayer.getName() != null && offlinePlayer.getUniqueId() != null && (!Properties.ENSURE_CORRECT_PLAYERID || offlinePlayer.getUniqueId().version() == uuidVersion)) { account = storeUsername(new PlayerDTO(offlinePlayer.getUniqueId(), offlinePlayer.getName())); } else { invalidPlayers.put(name.toLowerCase(Locale.ROOT), true); } } if (account != null) { return getAccount(account.getUuid()); // then get the last account that was online with that UUID } return null; } /** * Store the username of a player into the database and the username-uuid cache * * @param player The data transfer object of the player to store * @return The stored/updated account. null if there was an error updating it */ public static Account storeUsername(final PlayerDTO player) { final UUID uuid = player.getUniqueId(); Account latestAccount = null; try { latestAccount = accounts.queryBuilder().where().eq("uuid", new SelectArg(uuid)).and().eq("name", new SelectArg(player.getName())).queryForFirst(); } catch (SQLException e) { ChestShop.getBukkitLogger().log(Level.WARNING, "Error while searching for latest account of " + player.getName() + "/" + uuid + ":", e); } if (latestAccount == null) { latestAccount = new Account(player.getName(), getNewShortenedName(player), player.getUniqueId()); } latestAccount.setLastSeen(new Date()); try { accounts.createOrUpdate(latestAccount); } catch (SQLException e) { ChestShop.getBukkitLogger().log(Level.WARNING, "Error while updating account " + latestAccount + ":", e); return null; } usernameToAccount.put(latestAccount.getName(), latestAccount); uuidToAccount.put(uuid, latestAccount); shortToAccount.put(latestAccount.getShortName(), latestAccount); return latestAccount; } /** * Get a new unique shortened name that hasn't been used by another player yet * * @param player The player data to get the shortened name for * @return A new shortened name that hasn't been used before and is a maximum of 15 chars long */ private static String getNewShortenedName(PlayerDTO player) { String shortenedName = NameUtil.stripUsername(player.getName()); Account account = getAccountFromShortName(shortenedName); if (account == null) { return shortenedName; } for (int id = 0; account != null; id++) { String baseId = Base62.encode(id); shortenedName = NameUtil.stripUsername(player.getName(), 15 - 1 - baseId.length()) + ":" + baseId; account = getAccountFromShortName(shortenedName); } return shortenedName; } public static boolean canUseName(Player player, Permission base, String name) { if (ChestShopSign.isAdminShop(name)) { if (Permission.has(player, Permission.ADMIN_SHOP)) { return true; } else { ChestShop.logDebug(player.getName() + " cannot use the name " + name + " as it's an admin shop and they don't have the permission " + Permission.ADMIN_SHOP); return false; } } if (Permission.otherName(player, base, name)) { return true; } AccountQueryEvent queryEvent = new AccountQueryEvent(name); ChestShop.callEvent(queryEvent); Account account = queryEvent.getAccount(); if (account == null) { // There is no account by the provided name, but it matches the player name // Return true as they specified their own name and a new account should get created if (player.getName().equalsIgnoreCase(name)) { return true; } ChestShop.logDebug(player.getName() + " cannot use the name " + name + " for a shop as no account with that name exists"); return false; } if (!account.getName().equalsIgnoreCase(name) && Permission.otherName(player, base, account.getName())) { return true; } AccountAccessEvent event = new AccountAccessEvent(player, account); ChestShop.callEvent(event); return event.canAccess(); } @EventHandler public static void onAccountAccessCheck(AccountAccessEvent event) { if (!event.canAccess()) { event.setAccess(event.getPlayer().getUniqueId().equals(event.getAccount().getUuid())); if (!event.canAccess()) { ChestShop.logDebug(event.getPlayer().getName() + "/" + event.getPlayer().getUniqueId() + " cannot access the account " + event.getAccount().getName() + "/" + event.getAccount().getUuid() + " as their UUID doesn't match!"); } } } public static boolean isAdminShop(UUID uuid) { return adminAccount != null && uuid.equals(adminAccount.getUuid()); } public static boolean isServerEconomyAccount(UUID uuid) { return serverEconomyAccount != null && uuid.equals(serverEconomyAccount.getUuid()); } public static void load() { if (getUuidVersion() < 0) { if (Bukkit.getOnlineMode()) { setUuidVersion(4); } else if (!Bukkit.getOnlinePlayers().isEmpty()) { setUuidVersion(Bukkit.getOnlinePlayers().iterator().next().getUniqueId().version()); } } try { accounts = DaoCreator.getDaoAndCreateTable(Account.class); adminAccount = new Account(Properties.ADMIN_SHOP_NAME, Bukkit.getOfflinePlayer(Properties.ADMIN_SHOP_NAME).getUniqueId()); accounts.createOrUpdate(adminAccount); if (!Properties.SERVER_ECONOMY_ACCOUNT.isEmpty()) { serverEconomyAccount = getAccount(Properties.SERVER_ECONOMY_ACCOUNT); } if (serverEconomyAccount == null && !Properties.SERVER_ECONOMY_ACCOUNT.isEmpty() && !Properties.SERVER_ECONOMY_ACCOUNT_UUID.equals(new UUID(0, 0))) { serverEconomyAccount = getOrCreateAccount(Properties.SERVER_ECONOMY_ACCOUNT_UUID, Properties.SERVER_ECONOMY_ACCOUNT); } if (serverEconomyAccount == null || serverEconomyAccount.getUuid() == null) { serverEconomyAccount = null; if (!Properties.SERVER_ECONOMY_ACCOUNT.isEmpty()) { ChestShop.getBukkitLogger().log(Level.WARNING, "Server economy account setting '" + Properties.SERVER_ECONOMY_ACCOUNT + "' doesn't seem to be the name of a known player account!" + " Please specify the SERVER_ECONOMY_ACCOUNT_UUID" + " or log in at least once and create a player shop with that account" + " in order for the server economy account to work."); } } } catch (SQLException e) { ChestShop.getBukkitLogger().log(Level.SEVERE, "Error while trying to setup accounts", e); } } public static Account getServerEconomyAccount() { return serverEconomyAccount; } public static void setUuidVersion(int uuidVersion) { NameManager.uuidVersion = uuidVersion; } public static int getUuidVersion() { return uuidVersion; } }