/* * This file is part of Bitsquare. * * Bitsquare is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * Bitsquare 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 Affero General Public * License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. */ package io.bitsquare.trade.offer; import io.bitsquare.app.Version; import io.bitsquare.common.Timer; import io.bitsquare.common.UserThread; import io.bitsquare.storage.Storage; import io.bitsquare.trade.Tradable; import io.bitsquare.trade.TradableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Date; public final class OpenOffer implements Tradable { // That object is saved to disc. We need to take care of changes to not break deserialization. private static final long serialVersionUID = Version.LOCAL_DB_VERSION; private static final Logger log = LoggerFactory.getLogger(OpenOffer.class); // Timeout for offer reservation during takeoffer process. If deposit tx is not completed in that time we reset the offer to AVAILABLE state. private static final long TIMEOUT_SEC = 30; transient private Timer timeoutTimer; public enum State { AVAILABLE, RESERVED, CLOSED, CANCELED } private final Offer offer; private State state = State.AVAILABLE; transient private Storage<TradableList<OpenOffer>> storage; public OpenOffer(Offer offer, Storage<TradableList<OpenOffer>> storage) { this.offer = offer; this.storage = storage; } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { try { in.defaultReadObject(); // If we have a reserved state from the local db we reset it if (state == State.RESERVED) setState(State.AVAILABLE); } catch (Throwable t) { log.warn("Cannot be deserialized." + t.getMessage()); } } public Date getDate() { return offer.getDate(); } @Override public String getId() { return offer.getId(); } @Override public String getShortId() { return offer.getShortId(); } public Offer getOffer() { return offer; } public void setStorage(Storage<TradableList<OpenOffer>> storage) { this.storage = storage; } public void setState(State state) { log.trace("setState" + state); boolean changed = this.state != state; this.state = state; if (changed) storage.queueUpForSave(); // We keep it reserved for a limited time, if trade preparation fails we revert to available state if (this.state == State.RESERVED) startTimeout(); else stopTimeout(); } public State getState() { return state; } private void startTimeout() { stopTimeout(); timeoutTimer = UserThread.runAfter(() -> { log.debug("Timeout for resettin State.RESERVED reached"); if (state == State.RESERVED) setState(State.AVAILABLE); }, TIMEOUT_SEC); } private void stopTimeout() { if (timeoutTimer != null) { timeoutTimer.stop(); timeoutTimer = null; } } @Override public String toString() { return "OpenOffer{" + "\n\ttimeoutTimer=" + timeoutTimer + "\n\toffer=" + offer + "\n\tstate=" + state + "\n\tstorage=" + storage + '}'; } }