/* * 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 com.google.common.base.Preconditions; import com.google.common.base.Strings; import org.kegbot.app.util.TimeSeries; import org.kegbot.core.FlowManager.Clock; import org.kegbot.proto.Models.KegTap; import javax.annotation.Nullable; /** * A flow holds all state about an active pour in progress. */ public class Flow { /** Clock instance, used for updating timekeeping. */ private Clock mClock; /** All flows are reported on a meter. */ private final String mMeterName; /** Flow id for this instance. */ private final int mFlowId; /** Tap for this flow; may be null if tap was unknown at time of flow. */ @Nullable private final KegTap mTap; /** Authenticated user for this flow. If unset, the flow is anonymous. */ private String mUsername; /** Current volume record, in flow meter ticks. */ private int mTicks = 0; /** Time the flow was started, in {@link Clock#elapsedRealtime()}. */ private final long mStartTimeMillis; /** * Time the flow was ended, in {@link Clock#elapsedRealtime()}. * <p/> * Only meaningful when {@link #mIsFinished} is {@code true}. */ private long mEndTimeMillis; /** Time of the last call to {@link #addTicks(int)}. */ private long mLastUpdateTimeMillis; /** * Last "activity" time. This usually matches {@link #mLastUpdateTimeMillis}, but may be reset in * {@link #pokeActivity()}. */ private long mLastActivityTimeMillis; /** * Maximum idle time, in milliseconds. If zero, flow may remain idle indefinitely. */ private long mMaxIdleTimeMillis; /** Optional message added to the flow by the user. */ private String mShout = ""; /** Set to {@code true} when the flow is finished. */ private boolean mIsFinished = false; /** Image file attached to this flow. */ private String mImagePath; private final TimeSeries.Builder mTimeSeries = TimeSeries.newBuilder(100, true); public Flow(Clock clock, String meterName, int flowId, KegTap tap, long maxIdleTimeMs) { mClock = clock; mMeterName = meterName; mFlowId = flowId; mTap = tap; mMaxIdleTimeMillis = maxIdleTimeMs; mUsername = ""; mTicks = 0; mStartTimeMillis = clock.elapsedRealtime(); mLastUpdateTimeMillis = clock.elapsedRealtime(); mLastActivityTimeMillis = mLastUpdateTimeMillis; mTimeSeries.add(clock.elapsedRealtime(), 0); } @Override public String toString() { StringBuilder builder = new StringBuilder("Flow") .append(" id=").append(mFlowId) .append(" meterName=").append(mMeterName) .append(" finished=").append(mIsFinished) .append(" user=").append(mUsername) .append(" ticks=").append(getTicks()) .append(" volume_ml=").append(getVolumeMl()); if (mTap != null) { builder.append(" tap=").append(mTap.getId()); } if (!Strings.isNullOrEmpty(mImagePath)) { builder.append(" imagePath=").append(mImagePath); } if (!Strings.isNullOrEmpty(mShout)) { builder.append(" shout='").append(mShout).append("'"); } return builder.toString(); } /** Resets the flow's idle time. */ public void pokeActivity() { mLastActivityTimeMillis = mClock.elapsedRealtime(); } /** * Increments the flow by the specified number of ticks. * * @param ticks number of ticks to add */ public void addTicks(int ticks) { Preconditions.checkState(!mIsFinished, "Flow is already finished, cannot add ticks."); mTicks += ticks; long now = mClock.elapsedRealtime(); mLastUpdateTimeMillis = now; mLastActivityTimeMillis = now; mTimeSeries.add(now, ticks); } public String getMeterName() { return mMeterName; } public String getUsername() { return mUsername; } public void setUsername(String username) { Preconditions.checkState(!mIsFinished, "Flow is already finished, cannot set username."); mUsername = username; } public int getFlowId() { return mFlowId; } public KegTap getTap() { return mTap; } public double getVolumeMl() { if (mTap == null) { return 0; } final double factor = mTap.getMeter().getTicksPerMl(); if (factor <= 0) { return 0; } return mTicks / factor; } public int getTicks() { return mTicks; } public long getMaxIdleTimeMs() { return mMaxIdleTimeMillis; } public void setFinished() { Preconditions.checkState(!mIsFinished, "Flow is already finished, cannot finish again."); mIsFinished = true; mEndTimeMillis = mClock.elapsedRealtime(); mTimeSeries.add(mEndTimeMillis, 0); } public boolean isFinished() { return mIsFinished; } public long getDurationMs() { if (!mIsFinished) { return mClock.elapsedRealtime() - mStartTimeMillis; } return mEndTimeMillis - mStartTimeMillis; } public long getIdleTimeMs() { return mClock.elapsedRealtime() - mLastActivityTimeMillis; } public long getMsUntilIdle() { return Math.max(mMaxIdleTimeMillis - getIdleTimeMs(), 0); } public void setImage(String imagePath) { mImagePath = imagePath; } public void removeImage() { mImagePath = null; } public String getImagePath() { return mImagePath; } public void setShout(String shout) { if (shout == null) { mShout = ""; } else { mShout = shout; } } public String getShout() { return mShout; } public TimeSeries getTickTimeSeries() { return mTimeSeries.build(); } /** * Returns true if the flow has exceeded the maximum allowable idle time. * * @return */ public boolean isIdle() { if (mMaxIdleTimeMillis <= 0) { return false; } return getIdleTimeMs() >= mMaxIdleTimeMillis; } public boolean isAuthenticated() { return !Strings.isNullOrEmpty(mUsername); } public boolean isAnonymous() { return !isAuthenticated(); } }