package chatty.gui.components.admin; import chatty.util.api.CommunitiesManager; import chatty.util.api.CommunitiesManager.Community; import chatty.util.settings.Settings; import chatty.util.settings.SettingsListener; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; /** * * @author tduva */ public class StatusHistory implements SettingsListener { private static final Logger LOGGER = Logger.getLogger(StatusHistory.class.getName()); private static final int DAY = 1000*60*60*24; private static final int MAX_LOADING_ERRORS = 2; /** * The enties, whereas key and value should be equal, although the value * could contain different values for some of the properties, so it should * be used when returning a value. * * Using a Map to keep entries that are equal (same title/game) unique, but * still having the ability to pull the actual value out by a key. */ private final Map<StatusHistoryEntry, StatusHistoryEntry> entries = new HashMap<>(); /** * Reference to the Settings. */ private final Settings settings; /** * Create a new {@code StatusHistory} object, load the entries from settings * and remove old ones. * * The reference to the settings object is used to load/save the entries and * get the settings for removing old entries. * * @param settings */ public StatusHistory(Settings settings) { this.settings = settings; loadFromSettings(); removeOld(); } /** * Removes entries that are old as per the settings, except favorites. */ private synchronized void removeOld() { if (!settings.getBoolean("statusHistoryClear")) { return; } long days = settings.getLong("statusHistoryKeepDays"); long keepAfter = System.currentTimeMillis() - days*DAY; //System.out.println(keepAfter+" "+(days*DAY)); Iterator<Map.Entry<StatusHistoryEntry, StatusHistoryEntry>> it = entries.entrySet().iterator(); int countRemoved = 0; while (it.hasNext()) { Map.Entry<StatusHistoryEntry, StatusHistoryEntry> entry = it.next(); // Remove entry if it's not a favorite and older than specified in // the setting //System.out.println(entry.getValue().lastActivity); if (!entry.getValue().favorite && entry.getValue().lastActivity < keepAfter) { it.remove(); countRemoved++; } } if (countRemoved > 0) { LOGGER.info("StatusPresets: Removed "+countRemoved+" old entries."); } } /** * Return the entry for this title and game. * * @param title The title * @param game The game * @param community * @return */ public synchronized StatusHistoryEntry get(String title, String game, Community community) { StatusHistoryEntry entry = new StatusHistoryEntry(title, game, community); return entries.get(entry); } public synchronized boolean isFavorite(String title, String game, Community community) { StatusHistoryEntry entry = get(title, game, community); return entry != null ? entry.favorite : false; } private void put(StatusHistoryEntry entry) { entries.put(entry, entry); } /** * Add the entry with the given values. * * @param title * @param game * @param community * @param lastSet * @param timesUsed * @return */ public synchronized StatusHistoryEntry add(String title, String game, Community community, long lastSet, int timesUsed) { StatusHistoryEntry entry = new StatusHistoryEntry(title, game, community, lastSet, timesUsed, false); put(entry); return entry; } /** * Removes the entry with the given title and game. * * @param title * @param game * @param community */ public synchronized void remove(String title, String game, Community community) { StatusHistoryEntry entry = new StatusHistoryEntry(title, game, community); entries.remove(entry); } /** * Removes the given entry. * * @param entry The entry to remove */ public synchronized void remove(StatusHistoryEntry entry) { entries.remove(entry); } /** * Adds a new entry for the given title and game or modifies the already * existing one, if present. * * @param title * @param game * @param community * @return */ public synchronized StatusHistoryEntry addUsed(String title, String game, Community community) { StatusHistoryEntry entry = new StatusHistoryEntry(title, game, community, System.currentTimeMillis(), 1, false); StatusHistoryEntry present = entries.get(entry); if (present != null) { entry = present.increaseUsed(); } put(entry); return entry; } /** * Adds the given title and game to the favorites, creating a new entry * if not already present. * * @param title * @param game * @param community * @return */ public synchronized StatusHistoryEntry addFavorite(String title, String game, Community community) { return setFavorite(title, game, community, true); } /** * Removes the given title and game from the favorites. It actually adds * an entry if none is yet present for this, but with the favorite property * set to {@code false}. * * @param title * @param game * @param community */ public synchronized void removeFavorite(String title, String game, Community community) { setFavorite(title, game, community, false); } /** * Sets the favorite property for the entry with the given title and game. * * @param title * @param game * @param community * @param favorite * @return */ public synchronized StatusHistoryEntry setFavorite(String title, String game, Community community, boolean favorite) { StatusHistoryEntry entry = new StatusHistoryEntry(title, game, community, System.currentTimeMillis(), 0, favorite); return setFavorite(entry, favorite); } /** * Sets the favorite propery of the given entry. If a matching entry already * exists, then the given entry is only used as a key and the present entry * is modified, otherwise the given entry is added. * * @param entry * @param favorite * @return */ public synchronized StatusHistoryEntry setFavorite(StatusHistoryEntry entry, boolean favorite) { StatusHistoryEntry present = entries.get(entry); if (present != null) { entry = present; } if (entry.favorite != favorite) { entry = entry.setFavorite(favorite); } put(entry); return entry; } public synchronized void updateCommunityName(Community c) { Set<StatusHistoryEntry> modifiedValues = new HashSet<>(); for (StatusHistoryEntry entry : entries.values()) { StatusHistoryEntry modified = entry.updateCommunityName(c); if (modified != entry) { modifiedValues.add(modified); } } modifiedValues.stream().forEach(e -> entries.put(e, e)); } /** * Returns a copy of the entries list. * * @return */ public synchronized List<StatusHistoryEntry> getEntries() { return new ArrayList<>(entries.values()); } /** * Turns all entries into a list and adds them to a list that is saved * in the settings. */ private synchronized void saveToSettings() { List<List> entriesToSave = new ArrayList<>(); for (StatusHistoryEntry entry : entries.values()) { entriesToSave.add(entryToList(entry)); } settings.putList("statusPresets", entriesToSave); } /** * Goes through all entries saved in the settings (as a list of lists) and * tries to turn them into {@code StatusHistoryEntry} objects. */ private synchronized void loadFromSettings() { List<List> entriesToLoad = settings.getList("statusPresets"); entries.clear(); int count = 0; int errorCount = 0; for (List entryToLoad : entriesToLoad) { StatusHistoryEntry entry = listToEntry(entryToLoad); if (entry != null) { entries.put(entry, entry); count++; } else { if (errorCount < MAX_LOADING_ERRORS) { LOGGER.warning("StatusPresets: Couldn't load entry " + entryToLoad); } errorCount++; } } if (errorCount > MAX_LOADING_ERRORS) { LOGGER.warning("StatusPresets: "+(errorCount - MAX_LOADING_ERRORS)+" more errors."); } LOGGER.info("StatusPresets: Loaded "+count+"/"+entriesToLoad.size()+" entries."); } /** * Turns a {@code StatusHistoryEntry} into a {@code List}, so it can be * saved in the settings. * * Always returns the same order of elements, so the list can be read again * and turned into a {@code StatusHistoryEntry} again. * * @param entry The {@code StatusHistoryEntry} * @return The {@code List} * @see listToEntry(List) */ private List entryToList(StatusHistoryEntry entry) { List<Object> list = new ArrayList<>(); list.add(entry.title); list.add(entry.game); list.add(entry.lastActivity); list.add(entry.timesUsed); list.add(entry.favorite); if (entry.community != null) { list.add(entry.community.getId()); list.add(entry.community.getName()); } return list; } /** * Turns a list into a {@code StatusHistoryEntry}. The list should be: * {title, game, lastSet, timesUsed, favorite} * * If the list has the wrong size or contains the wrong types no entry is * created. * * @param list * @return The {@code StatusHistoryEntry} or {@code null} if an error * occured * @see entryToList(StatusHistoryEntry) */ private StatusHistoryEntry listToEntry(List list) { try { String title = (String) list.get(0); String game = (String) list.get(1); Number lastSet = (Number) list.get(2); Number timesUsed = (Number) list.get(3); Boolean favorite = (Boolean) list.get(4); Community community = Community.EMPTY; if (list.size() > 5) { String communityId = (String) list.get(5); String communityName = (String) list.get(6); community = new Community(communityId, communityName); } if (title == null || game == null) { //LOGGER.warning("Didn't load "+list+" (Unexpected null)"); return null; } return new StatusHistoryEntry(title, game, community, lastSet.longValue(), timesUsed.intValue(), favorite); } catch (ClassCastException | IndexOutOfBoundsException ex) { //LOGGER.warning("Didn't load "+list+" ("+ex.getLocalizedMessage()+")"); return null; } } /** * Called when the settings are about to be saved to file, so save to * settings now. */ @Override public void aboutToSaveSettings(Settings settings) { saveToSettings(); } }