/* * Copyright 2014 Bevbot LLC <info@bevbot.com> * * This file is part of the Kegtab package from the Kegbot project. For * more information on Kegtab or Kegbot, see <http://kegbot.org/>. * * Kegtab is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation, version 2. * * Kegtab is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with Kegtab. If not, see <http://www.gnu.org/licenses/>. */ package org.kegbot.core; import android.util.Log; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.squareup.otto.Bus; import com.squareup.otto.Produce; import com.squareup.otto.Subscribe; import org.kegbot.app.config.ConfigurationStore; import org.kegbot.app.event.TapsChangedEvent; import org.kegbot.app.event.VisibleTapsChangedEvent; import org.kegbot.app.util.IndentingPrintWriter; import org.kegbot.proto.Models.KegTap; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; /** * Tap manager. * * @author mike wakerly (mike@wakerly.com) */ public class TapManager extends Manager { private static final String TAG = TapManager.class.getSimpleName(); @VisibleForTesting protected static final String KEY_HIDDEN_TAP_IDS = "hidden_tap_ids"; /** Sorts taps by (sort_order, id). */ private static final Comparator<KegTap> TAP_COMPARATOR = new Comparator<KegTap>() { @Override public int compare(KegTap kegTap, KegTap kegTap2) { if (kegTap == kegTap2) { return 0; } else if (kegTap == null) { return -1; } else if (kegTap2 == null) { return 1; } int ret = kegTap.getSortOrder() - kegTap2.getSortOrder(); if (ret == 0) { ret = kegTap.getId() - kegTap2.getId(); } return ret; } }; private final Map<Integer, KegTap> mTaps = Maps.newLinkedHashMap(); private ConfigurationStore mLocalConfig; public TapManager(Bus bus, ConfigurationStore configStore) { super(bus); mLocalConfig = configStore; } @Override protected synchronized void start() { getBus().register(this); } @Override protected synchronized void stop() { getBus().unregister(this); mTaps.clear(); super.stop(); } /** * Adds a Tap to the system. * * @param newTap the new tap object * @return {@code true} if an existing tap was replaced, {@code false} otherwise. */ synchronized boolean addTap(final KegTap newTap) { Log.i(TAG, "Adding/updating tap " + newTap.getId()); final Integer tapId = Integer.valueOf(newTap.getId()); return mTaps.put(Integer.valueOf(newTap.getId()), newTap) != null; } /** * Removes a tap from the system. * * @param tap the tap to remove * @return {@code true} if an existing tap was removed, {@code false} otherwise. */ synchronized boolean removeTap(final KegTap tap) { Log.i(TAG, "Removing tap " + tap.getId()); setTapVisibility(tap, true); // clear invisibility return mTaps.remove(Integer.valueOf(tap.getId())) != null; } /** * Updates the set of installed taps to match {@code taps}. * * @param taps */ public synchronized void updateTaps(final Collection<KegTap> taps) { boolean updated = false; final Set<Integer> tapsToRemove = Sets.newLinkedHashSet(mTaps.keySet()); for (final KegTap tap : taps) { final Integer key = Integer.valueOf(tap.getId()); tapsToRemove.remove(key); if (mTaps.containsKey(key) && getTap(key.intValue()).equals(tap)) { continue; } updated = true; addTap(tap); } for (final Integer tapId : tapsToRemove) { updated = true; removeTap(mTaps.get(tapId)); } if (updated) { postUpdate(); } } private void postUpdate() { postOnMainThread(produceTapsEvent()); postOnMainThread(productVisibleTapsEvent()); } @Produce public TapsChangedEvent produceTapsEvent() { return new TapsChangedEvent(Lists.newArrayList(getTaps())); } @Produce public VisibleTapsChangedEvent productVisibleTapsEvent() { return new VisibleTapsChangedEvent(Lists.newArrayList(getVisibleTaps())); } public synchronized KegTap getTap(int tapId) { return mTaps.get(Integer.valueOf(tapId)); } @Deprecated public synchronized KegTap getTapForMeterName(final String meterName) { for (final KegTap tap : mTaps.values()) { if (!tap.hasMeter()) { continue; } if (meterName.equals(tap.getMeter().getName())) { return tap; } } return null; } /** Returns all taps known to the backend, in sorted order. */ public synchronized List<KegTap> getTaps() { final List<KegTap> result = Lists.newArrayList(mTaps.values()); Collections.sort(result, TAP_COMPARATOR); return result; } /** Returns all locally-visible taps, in sorted order. */ public synchronized List<KegTap> getVisibleTaps() { final Set<String> hiddenIds = mLocalConfig.getStringSet(KEY_HIDDEN_TAP_IDS, Collections.<String>emptySet()); final List<KegTap> results = Lists.newArrayList(); for (final KegTap tap : mTaps.values()) { final String tapId = String.valueOf(tap.getId()); if (!hiddenIds.contains(tapId)) { results.add(tap); } } Collections.sort(results, TAP_COMPARATOR); return results; } public synchronized Collection<KegTap> getTapsWithActiveKeg() { final Set<KegTap> result = Sets.newLinkedHashSet(); for (final KegTap tap : mTaps.values()) { if (tap.hasCurrentKeg()) { result.add(tap); } } return result; } @Subscribe public synchronized void onTapSyncResults(TapsChangedEvent event) { final List<KegTap> taps = event.getTaps(); final Set<Integer> removedTaps = Sets.newLinkedHashSet(mTaps.keySet()); for (final KegTap tap : taps) { final KegTap existingTap = getTap(tap.getId()); removedTaps.remove(Integer.valueOf(tap.getId())); if (existingTap == null || !existingTap.equals(tap)) { addTap(tap); } } for (final Integer tapId : removedTaps) { Log.i(TAG, "Removing tap: " + tapId); removeTap(getTap(tapId.intValue())); } } public synchronized void setTapVisibility(KegTap tap, boolean isVisible) { final String tapId = String.valueOf(tap.getId()); final Set<String> hiddenTaps = mLocalConfig.getStringSet(KEY_HIDDEN_TAP_IDS, Sets.<String>newLinkedHashSet()); final boolean changed; if (isVisible) { changed = hiddenTaps.remove(tapId); } else { changed = hiddenTaps.add(tapId); } if (changed) { Log.d(TAG, "Setting tap " + tap.getId() + " visible=" + isVisible); mLocalConfig.putStringSet(KEY_HIDDEN_TAP_IDS, hiddenTaps); postUpdate(); } } public synchronized boolean getTapVisibility(KegTap tap) { return !mLocalConfig.getStringSet(KEY_HIDDEN_TAP_IDS, Sets.<String>newLinkedHashSet()).contains(String.valueOf(tap.getId())); } @Override protected synchronized void dump(IndentingPrintWriter writer) { writer.printPair("numTaps", Integer.valueOf(mTaps.size())).println(); if (mTaps.size() > 0) { writer.println(); writer.println("All taps:"); writer.println(); writer.increaseIndent(); for (final KegTap tap : mTaps.values()) { writer.printPair("tap", tap).println(); writer.println(); } writer.decreaseIndent(); } } }