package chatty; import chatty.util.api.usericons.UsericonManager; import chatty.util.BotNameManager; import java.util.Map.Entry; import java.util.*; import java.util.logging.Logger; /** * Provides methods to get a (maybe new) User object for a channel/username * combination and search for User objects by channel, username etc. * * With IRCv3 this doesn't save a lot of state anymore, because every message * has the user type etc. it is only saved directly in the User objects. * Although it could be useful to add some caching again (e.g. for showing * user type in userlist before the user said something). * * @author tduva */ public class UserManager { private static final Logger LOGGER = Logger.getLogger(UserManager.class.getName()); private final Set<UserManagerListener> listeners = new HashSet<>(); private volatile String localUsername; public final User specialUser = new User("[specialUser]", "[nochannel]"); private final HashMap<String, HashMap<String, User>> users = new HashMap<>(); private final HashMap<String, String> cachedColors = new HashMap<>(); private boolean capitalizedNames = false; private final Map<Integer, String> emotesets = Collections.synchronizedMap(new HashMap<Integer, String>()); private final User errorUser = new User("[Error]", "#[error]"); // Stupid hack to get Usericons in ChannelTextPane without a user (twitchnotify messages) public final User dummyUser = new User("", "#[error]"); private CustomNames customNamesManager; private UsericonManager usericonManager; private UsercolorManager usercolorManager; private Addressbook addressbook; private BotNameManager botNameManager; public void setLocalUsername(String username) { this.localUsername = username; } public void setBotNameManager(BotNameManager m) { this.botNameManager = m; m.addListener(new BotNameManager.BotNameListener() { private void setUserBot(User user) { if (user.setBot(true)) { userUpdated(user); } } @Override public void botNameAdded(String channel, String botName) { if (channel == null) { for (User user : getUsersByName(botName)) { setUserBot(user); } } else { User user = getUserIfExists(channel, botName); if (user != null) { setUserBot(user); } } } }); } public void addListener(UserManagerListener listener) { if (listener != null) { listeners.add(listener); } } private void userUpdated(User user) { for (UserManagerListener listener : listeners) { listener.userUpdated(user); } } public void setCapitalizedNames(boolean capitalized) { capitalizedNames = capitalized; } public void setUsericonManager(UsericonManager manager) { usericonManager = manager; dummyUser.setUsericonManager(manager); } public void setUsercolorManager(UsercolorManager manager) { usercolorManager = manager; } public void setAddressbook(Addressbook addressbook) { this.addressbook = addressbook; } public void setEmotesets(Map<Integer, String> newEmotesets) { emotesets.putAll(newEmotesets); } public void setCustomNamesManager(CustomNames m) { if (m != null) { this.customNamesManager = m; m.addListener(new CustomNames.CustomNamesListener() { @Override public void setName(String name, String customNick) { List<User> users = getUsersByName(name); for (User user : users) { user.setCustomNick(customNick); userUpdated(user); } } }); } } /** * Gets a Map of all User objects in the given channel. * * @param channel * @return */ public synchronized HashMap<String, User> getUsersByChannel(String channel) { HashMap<String, User> result = users.get(channel); if (result == null) { result = new HashMap<>(); users.put(channel, result); } return result; } /** * Searches all channels for the given username and returns a List of all * the associated User objects. Does not create User object, only return * existing ones. * * @param name The username to search for * @return The List of User-objects. */ public synchronized List<User> getUsersByName(String name) { name = name.toLowerCase(); List<User> result = new ArrayList<>(); Iterator<HashMap<String, User>> it = users.values().iterator(); while (it.hasNext()) { HashMap<String, User> channelUsers = it.next(); User user = channelUsers.get(name); if (user != null) { result.add(user); } } return result; } /** * Returns the user for the given channel and name, but only if an object * already exists. * * @param channel * @param name * @return The {@code User} object or null if none exists */ public synchronized User getUserIfExists(String channel, String name) { return getUsersByChannel(channel).get(name); } /** * Returns the User with the given name or creates a new User object if none * exists for this name. * * @param channel * @param name The name of the user * @return The matching User object * @see User */ public synchronized User getUser(String channel, String name) { // Not sure if this makes sense if (name == null || name.isEmpty()) { return errorUser; } String displayName = name; name = name.toLowerCase(Locale.ENGLISH); User user = getUserIfExists(channel, name); if (user == null) { String capitalizedName = null; if (displayName.equals(name)) { if (capitalizedName != null) { displayName = capitalizedName; } else if (capitalizedNames) { displayName = name.substring(0, 1).toUpperCase() + name.substring(1); } } user = new User(displayName, capitalizedName, channel); user.setUsercolorManager(usercolorManager); user.setAddressbook(addressbook); user.setUsericonManager(usericonManager); if (customNamesManager != null) { user.setCustomNick(customNamesManager.getCustomName(name)); } if (botNameManager != null && botNameManager.isBotName(channel, name)) { user.setBot(true); } // Initialize some values if present for this name if (cachedColors.containsKey(name)) { user.setColor(cachedColors.get(name)); } if (name.equals(localUsername)) { /** * Set initial data for local user that is globally valid. This * data would have been received from the GLOBALUSERSTATE * command which may not be send after every join or sent * message. */ user.setAdmin(specialUser.isAdmin()); user.setGlobalMod(specialUser.isGlobalMod()); user.setStaff(specialUser.isStaff()); user.setTurbo(specialUser.hasTurbo()); if (!specialUser.hasDefaultColor()) { user.setColor(specialUser.getPlainColor()); } user.setEmoteSets(specialUser.getEmoteSet()); if (specialUser.hasDisplayNickSet()) { user.setDisplayNick(specialUser.getDisplayNick()); } } // Put User into the map for the channel getUsersByChannel(channel).put(name, user); } return user; } /** * Searches all channels for the given username and returns a Map with * all channels the username was found in and the associated User objects. * * @param name The username to be searched for * @return A Map with channel->User association */ public synchronized HashMap<String,User> getChannelsAndUsersByUserName(String name) { String lowercaseName = name.toLowerCase(Locale.ENGLISH); HashMap<String,User> result = new HashMap<>(); Iterator<Entry<String, HashMap<String, User>>> it = users.entrySet().iterator(); while (it.hasNext()) { Entry<String, HashMap<String, User>> channel = it.next(); String channelName = channel.getKey(); HashMap<String,User> channelUsers = channel.getValue(); User user = channelUsers.get(lowercaseName); if (user != null) { result.put(channelName,user); } } return result; } /** * Remove all users. */ public synchronized void clear() { users.clear(); } /** * Remove all users of the given channel. * * @param channel */ public synchronized void clear(String channel) { getUsersByChannel(channel).clear(); } /** * Set all users offline. */ public synchronized void setAllOffline() { Iterator<HashMap<String,User>> it = users.values().iterator(); while (it.hasNext()) { setAllOffline(it.next()); } } /** * Set all users of the given channel offline. * * @param channel */ public synchronized void setAllOffline(String channel) { if (channel == null) { setAllOffline(); } Map<String, User> usersInChannel = users.get(channel); if (usersInChannel != null) { setAllOffline(usersInChannel); } } /** * Set all given users offline. Helper method. * * @param usersInChannel */ private void setAllOffline(Map<String, User> usersInChannel) { for (User user : usersInChannel.values()) { user.setOnline(false); } } /** * Sets the color of a user across all channels. * * @param userName String The name of the user * @param color String The color as a string representation */ protected synchronized void setColorForUsername(String userName, String color) { userName = userName.toLowerCase(); cachedColors.put(userName,color); List<User> userAllChans = getUsersByName(userName); for (User user : userAllChans) { user.setColor(color); } } /** * The list of mods received with channel context, set the containing names * as mod. Returns the changed users so they can be updated in the GUI. * * @param channel * @param modsList * @return */ protected synchronized List<User> modsListReceived(String channel, List<String> modsList) { // Demod everyone on the channel Map<String,User> usersToDemod = getUsersByChannel(channel); for (User user : usersToDemod.values()) { user.setModerator(false); } // Mod everyone in the list LOGGER.info("Setting users as mod for "+channel+": "+modsList); List<User> changedUsers = new ArrayList<>(); for (String userName : modsList) { if (Helper.validateChannel(userName)) { User user = getUser(channel, userName); if (user.setModerator(true)) { userUpdated(user); } changedUsers.add(user); } } return changedUsers; } public static interface UserManagerListener { public void userUpdated(User user); } }