Fix remove offer bug. Changed polling, fix problems with empty offer list.

This commit is contained in:
Manfred Karrer 2014-09-16 15:05:05 +02:00
parent 333c367ded
commit 24108cdcde
7 changed files with 130 additions and 101 deletions

View file

@ -54,7 +54,7 @@ public class OrderBook {
private final ObservableList<OrderBookListItem> orderBookListItems = FXCollections.observableArrayList(); private final ObservableList<OrderBookListItem> orderBookListItems = FXCollections.observableArrayList();
private final OrderBookListener orderBookListener; private final OrderBookListener orderBookListener;
private final ChangeListener<BankAccount> bankAccountChangeListener; private final ChangeListener<BankAccount> bankAccountChangeListener;
private final ChangeListener<Boolean> dirtyListener; private final ChangeListener<Number> invalidationListener;
private String fiatCode; private String fiatCode;
private AnimationTimer pollingTimer; private AnimationTimer pollingTimer;
private Country country; private Country country;
@ -71,7 +71,11 @@ public class OrderBook {
this.user = user; this.user = user;
bankAccountChangeListener = (observableValue, oldValue, newValue) -> setBankAccount(newValue); bankAccountChangeListener = (observableValue, oldValue, newValue) -> setBankAccount(newValue);
dirtyListener = (ov, oldValue, newValue) -> requestOffers(); invalidationListener = (ov, oldValue, newValue) -> {
log.debug("#### oldValue " + oldValue);
log.debug("#### newValue " + newValue);
requestOffers();
};
orderBookListener = new OrderBookListener() { orderBookListener = new OrderBookListener() {
@Override @Override
public void onOfferAdded(Offer offer) { public void onOfferAdded(Offer offer) {
@ -87,7 +91,7 @@ public class OrderBook {
@Override @Override
public void onOfferRemoved(Offer offer) { public void onOfferRemoved(Offer offer) {
orderBookListItems.removeIf(item -> item.getOffer().equals(offer)); orderBookListItems.removeIf(item -> item.getOffer().getId().equals(offer.getId()));
} }
}; };
} }
@ -137,15 +141,17 @@ public class OrderBook {
} }
private void addListeners() { private void addListeners() {
log.trace("addListeners ");
user.currentBankAccountProperty().addListener(bankAccountChangeListener); user.currentBankAccountProperty().addListener(bankAccountChangeListener);
messageFacade.addOrderBookListener(orderBookListener); messageFacade.addOrderBookListener(orderBookListener);
messageFacade.getIsDirtyProperty().addListener(dirtyListener); messageFacade.invalidationTimestampProperty().addListener(invalidationListener);
} }
private void removeListeners() { private void removeListeners() {
log.trace("removeListeners ");
user.currentBankAccountProperty().removeListener(bankAccountChangeListener); user.currentBankAccountProperty().removeListener(bankAccountChangeListener);
messageFacade.removeOrderBookListener(orderBookListener); messageFacade.removeOrderBookListener(orderBookListener);
messageFacade.getIsDirtyProperty().removeListener(dirtyListener); messageFacade.invalidationTimestampProperty().removeListener(invalidationListener);
} }
private void addOfferToOrderBookListItems(Offer offer) { private void addOfferToOrderBookListItems(Offer offer) {
@ -155,6 +161,7 @@ public class OrderBook {
} }
private void requestOffers() { private void requestOffers() {
log.debug("requestOffers");
messageFacade.getOffers(fiatCode); messageFacade.getOffers(fiatCode);
} }
@ -168,7 +175,7 @@ public class OrderBook {
addListeners(); addListeners();
setBankAccount(user.getCurrentBankAccount()); setBankAccount(user.getCurrentBankAccount());
pollingTimer = Utilities.setInterval(1000, (animationTimer) -> { pollingTimer = Utilities.setInterval(1000, (animationTimer) -> {
messageFacade.getDirtyFlag(fiatCode); messageFacade.getInvalidationTimeStamp(fiatCode);
return null; return null;
}); });

View file

@ -64,7 +64,7 @@ public class OrderBookModel extends UIModel {
private final FilteredList<OrderBookListItem> filteredItems; private final FilteredList<OrderBookListItem> filteredItems;
private final SortedList<OrderBookListItem> sortedItems; private final SortedList<OrderBookListItem> sortedItems;
private OrderBookInfo orderBookInfo; private OrderBookInfo orderBookInfo;
private final ChangeListener<BankAccount> bankAccountChangeListener; private ChangeListener<BankAccount> bankAccountChangeListener;
private final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>(); private final ObjectProperty<Coin> amountAsCoin = new SimpleObjectProperty<>();
private final ObjectProperty<Fiat> priceAsFiat = new SimpleObjectProperty<>(); private final ObjectProperty<Fiat> priceAsFiat = new SimpleObjectProperty<>();
@ -93,16 +93,16 @@ public class OrderBookModel extends UIModel {
filteredItems = new FilteredList<>(orderBook.getOrderBookListItems()); filteredItems = new FilteredList<>(orderBook.getOrderBookListItems());
sortedItems = new SortedList<>(filteredItems); sortedItems = new SortedList<>(filteredItems);
bankAccountChangeListener = (observableValue, oldValue, newValue) -> setBankAccount(newValue);
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle // Lifecycle
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("EmptyMethod")
@Override @Override
public void initialize() { public void initialize() {
bankAccountChangeListener = (observableValue, oldValue, newValue) -> setBankAccount(newValue);
super.initialize(); super.initialize();
} }
@ -176,7 +176,6 @@ public class OrderBookModel extends UIModel {
} }
boolean isTradable(Offer offer) { boolean isTradable(Offer offer) {
log.debug("### isMatchingRestrictions " + offer);
// if user has not registered yet we display all // if user has not registered yet we display all
if (user.getCurrentBankAccount() == null) if (user.getCurrentBankAccount() == null)
return true; return true;

View file

@ -18,6 +18,7 @@
package io.bitsquare.msg; package io.bitsquare.msg;
import io.bitsquare.arbitrator.Arbitrator; import io.bitsquare.arbitrator.Arbitrator;
import io.bitsquare.msg.listeners.AddOfferListener;
import io.bitsquare.msg.listeners.ArbitratorListener; import io.bitsquare.msg.listeners.ArbitratorListener;
import io.bitsquare.msg.listeners.BootstrapListener; import io.bitsquare.msg.listeners.BootstrapListener;
import io.bitsquare.msg.listeners.GetPeerAddressListener; import io.bitsquare.msg.listeners.GetPeerAddressListener;
@ -44,8 +45,8 @@ import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleLongProperty;
import net.tomp2p.dht.FutureGet; import net.tomp2p.dht.FutureGet;
import net.tomp2p.dht.FuturePut; import net.tomp2p.dht.FuturePut;
@ -73,27 +74,16 @@ import org.slf4j.LoggerFactory;
* TODO: improve callbacks that Platform.runLater is not necessary. We call usually that methods form teh UI thread. * TODO: improve callbacks that Platform.runLater is not necessary. We call usually that methods form teh UI thread.
*/ */
public class MessageFacade implements MessageBroker { public class MessageFacade implements MessageBroker {
public static interface AddOfferListener {
void onComplete();
void onFailed(String reason, Throwable throwable);
}
private static final Logger log = LoggerFactory.getLogger(MessageFacade.class); private static final Logger log = LoggerFactory.getLogger(MessageFacade.class);
private static final String ARBITRATORS_ROOT = "ArbitratorsRoot"; private static final String ARBITRATORS_ROOT = "ArbitratorsRoot";
public P2PNode getP2pNode() {
return p2pNode;
}
private final P2PNode p2pNode; private final P2PNode p2pNode;
private final User user;
private final List<OrderBookListener> orderBookListeners = new ArrayList<>(); private final List<OrderBookListener> orderBookListeners = new ArrayList<>();
private final List<ArbitratorListener> arbitratorListeners = new ArrayList<>(); private final List<ArbitratorListener> arbitratorListeners = new ArrayList<>();
private final List<IncomingTradeMessageListener> incomingTradeMessageListeners = new ArrayList<>(); private final List<IncomingTradeMessageListener> incomingTradeMessageListeners = new ArrayList<>();
private final User user; private final LongProperty invalidationTimestamp = new SimpleLongProperty(0);
private SeedNodeAddress.StaticSeedNodeAddresses defaultStaticSeedNodeAddresses;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -174,13 +164,11 @@ public class MessageFacade implements MessageBroker {
try { try {
final Data offerData = new Data(offer); final Data offerData = new Data(offer);
log.trace("Add offer to DHT requested. Added data: [locationKey: " + locationKey +
", value: " + offerData + "]");
// the offer is default 30 days valid // the offer is default 30 days valid
int defaultOfferTTL = 30 * 24 * 60 * 60; int defaultOfferTTL = 30 * 24 * 60 * 60;
offerData.ttlSeconds(defaultOfferTTL); offerData.ttlSeconds(defaultOfferTTL);
log.trace("Add offer to DHT requested. Added data: [locationKey: " + locationKey +
", hash: " + offerData.hash().toString() + "]");
FuturePut futurePut = p2pNode.addProtectedData(locationKey, offerData); FuturePut futurePut = p2pNode.addProtectedData(locationKey, offerData);
futurePut.addListener(new BaseFutureListener<BaseFuture>() { futurePut.addListener(new BaseFutureListener<BaseFuture>() {
@Override @Override
@ -201,7 +189,7 @@ public class MessageFacade implements MessageBroker {
}); });
// TODO will be removed when we don't use polling anymore // TODO will be removed when we don't use polling anymore
setDirty(locationKey); updateInvalidationTimestamp(locationKey);
log.trace("Add offer to DHT was successful. Added data: [locationKey: " + locationKey + log.trace("Add offer to DHT was successful. Added data: [locationKey: " + locationKey +
", value: " + offerData + "]"); ", value: " + offerData + "]");
}); });
@ -237,10 +225,8 @@ public class MessageFacade implements MessageBroker {
Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode()); Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode());
try { try {
final Data offerData = new Data(offer); final Data offerData = new Data(offer);
log.trace("Remove offer from DHT requested. Removed data: [locationKey: " + locationKey + log.trace("Remove offer from DHT requested. Removed data: [locationKey: " + locationKey +
", value: " + offerData + "]"); ", hash: " + offerData.hash().toString() + "]");
FutureRemove futureRemove = p2pNode.removeFromDataMap(locationKey, offerData); FutureRemove futureRemove = p2pNode.removeFromDataMap(locationKey, offerData);
futureRemove.addListener(new BaseFutureListener<BaseFuture>() { futureRemove.addListener(new BaseFutureListener<BaseFuture>() {
@Override @Override
@ -258,7 +244,7 @@ public class MessageFacade implements MessageBroker {
log.error("Remove offer from DHT failed. Error: " + e.getMessage()); log.error("Remove offer from DHT failed. Error: " + e.getMessage());
} }
}); });
setDirty(locationKey); updateInvalidationTimestamp(locationKey);
}); });
log.trace("Remove offer from DHT was successful. Removed data: [key: " + locationKey + ", " + log.trace("Remove offer from DHT was successful. Removed data: [key: " + locationKey + ", " +
@ -283,6 +269,7 @@ public class MessageFacade implements MessageBroker {
public void getOffers(String currencyCode) { public void getOffers(String currencyCode) {
Number160 locationKey = Number160.createHash(currencyCode); Number160 locationKey = Number160.createHash(currencyCode);
log.trace("Get offers from DHT requested for locationKey: " + locationKey);
FutureGet futureGet = p2pNode.getDataMap(locationKey); FutureGet futureGet = p2pNode.getDataMap(locationKey);
futureGet.addListener(new BaseFutureAdapter<BaseFuture>() { futureGet.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override @Override
@ -306,14 +293,21 @@ public class MessageFacade implements MessageBroker {
listener.onOffersReceived(offers))); listener.onOffersReceived(offers)));
} }
//log.trace("Get offers from DHT was successful"); log.trace("Get offers from DHT was successful. Stored data: [key: " + locationKey
/* log.trace("Get offers from DHT was successful. Stored data: [key: " + locationKey + ", values: " + futureGet.dataMap() + "]");
+ ", values: " + futureGet.dataMap() + "]");*/ }
else {
final Map<Number640, Data> dataMap = futureGet.dataMap();
if (dataMap == null || dataMap.size() == 0) {
log.trace("Get offers from DHT delivered empty dataMap.");
Platform.runLater(() -> orderBookListeners.stream().forEach(listener ->
listener.onOffersReceived(new ArrayList<>())));
} }
else { else {
log.error("Get offers from DHT was not successful with reason:" + baseFuture.failedReason()); log.error("Get offers from DHT was not successful with reason:" + baseFuture.failedReason());
} }
} }
}
}); });
} }
@ -444,81 +438,86 @@ public class MessageFacade implements MessageBroker {
} }
/*
* We store the timestamp of any change of the offer list (add, remove offer) and we poll in intervals for changes.
* If we detect a change we request the offer list from the DHT.
* Polling should be replaced by a push based solution later.
*/
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Check dirty flag for a location key // Polling
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// TODO just temporary public void updateInvalidationTimestamp(Number160 locationKey) {
public BooleanProperty getIsDirtyProperty() { invalidationTimestamp.set(System.currentTimeMillis());
return isDirty; try {
FuturePut putFuture = p2pNode.putData(getInvalidatedLocationKey(locationKey),
new Data(invalidationTimestamp.get()));
putFuture.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
if (putFuture.isSuccess())
log.trace("Update invalidationTimestamp to DHT was successful. TimeStamp=" +
invalidationTimestamp.get());
else
log.error("Update invalidationTimestamp to DHT failed with reason:" + putFuture.failedReason());
} }
public void getDirtyFlag(String currencyCode) { @Override
public void exceptionCaught(Throwable t) throws Exception {
log.error("Update invalidationTimestamp to DHT failed with exception:" + t.getMessage());
}
});
} catch (IOException | ClassNotFoundException e) {
log.error("Update invalidationTimestamp to DHT failed with exception:" + e.getMessage());
}
}
public LongProperty invalidationTimestampProperty() {
return invalidationTimestamp;
}
public void getInvalidationTimeStamp(String currencyCode) {
Number160 locationKey = Number160.createHash(currencyCode); Number160 locationKey = Number160.createHash(currencyCode);
try { try {
FutureGet getFuture = p2pNode.getData(getDirtyLocationKey(locationKey)); FutureGet getFuture = p2pNode.getData(getInvalidatedLocationKey(locationKey));
getFuture.addListener(new BaseFutureListener<BaseFuture>() { getFuture.addListener(new BaseFutureListener<BaseFuture>() {
@Override @Override
public void operationComplete(BaseFuture future) throws Exception { public void operationComplete(BaseFuture future) throws Exception {
if (getFuture.isSuccess()) {
Data data = getFuture.data(); Data data = getFuture.data();
if (data != null) { if (data != null && data.object() instanceof Long) {
Object object = data.object(); final Object object = data.object();
if (object instanceof Long) { Platform.runLater(() -> {
Platform.runLater(() -> onGetDirtyFlag((Long) object)); Long timeStamp = (Long) object;
log.trace("Get invalidationTimestamp from DHT was successful. TimeStamp=" +
timeStamp);
invalidationTimestamp.set(timeStamp);
});
} }
else {
log.error("Get invalidationTimestamp from DHT failed. Data = " + data);
}
}
else {
log.error("Get invalidationTimestamp from DHT failed with reason:" + getFuture.failedReason());
} }
} }
@Override @Override
public void exceptionCaught(Throwable t) throws Exception { public void exceptionCaught(Throwable t) throws Exception {
log.error("getFuture exceptionCaught " + t.toString()); log.error("Get invalidationTimestamp from DHT failed with exception:" + t.getMessage());
t.printStackTrace();
} }
}); });
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
log.error("Get invalidationTimestamp from DHT failed with exception:" + e.getMessage());
e.printStackTrace(); e.printStackTrace();
} }
} }
private Long lastTimeStamp = -3L; private Number160 getInvalidatedLocationKey(Number160 locationKey) {
private final BooleanProperty isDirty = new SimpleBooleanProperty(false); return Number160.createHash(locationKey + "invalidated");
private void onGetDirtyFlag(long timeStamp) {
// TODO don't get updates at first execute....
if (lastTimeStamp != timeStamp) {
isDirty.setValue(!isDirty.get());
}
if (lastTimeStamp > 0) {
lastTimeStamp = timeStamp;
}
else {
lastTimeStamp++;
}
}
public void setDirty(Number160 locationKey) {
// we don't want to get an update from dirty for own changes, so update the lastTimeStamp to omit a change
// trigger
lastTimeStamp = System.currentTimeMillis();
try {
FuturePut putFuture = p2pNode.putData(getDirtyLocationKey(locationKey), new Data(lastTimeStamp));
putFuture.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
// log.trace("operationComplete");
}
@Override
public void exceptionCaught(Throwable t) throws Exception {
log.warn("Error at writing dirty flag (timeStamp) " + t.toString());
}
});
} catch (IOException | ClassNotFoundException e) {
log.warn("Error at writing dirty flag (timeStamp) " + e.getMessage());
}
}
private Number160 getDirtyLocationKey(Number160 locationKey) {
return Number160.createHash(locationKey + "Dirty");
} }
@ -529,7 +528,6 @@ public class MessageFacade implements MessageBroker {
@Override @Override
public void handleMessage(Object message, PeerAddress peerAddress) { public void handleMessage(Object message, PeerAddress peerAddress) {
if (message instanceof TradeMessage) { if (message instanceof TradeMessage) {
log.error("####################");
Platform.runLater(() -> incomingTradeMessageListeners.stream().forEach(e -> Platform.runLater(() -> incomingTradeMessageListeners.stream().forEach(e ->
e.onMessage((TradeMessage) message, peerAddress))); e.onMessage((TradeMessage) message, peerAddress)));
} }

View file

@ -0,0 +1,24 @@
/*
* 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.msg.listeners;
public interface AddOfferListener {
void onComplete();
void onFailed(String reason, Throwable throwable);
}

View file

@ -47,7 +47,8 @@ public class Offer implements Serializable {
private final Date creationDate; private final Date creationDate;
private final Fiat price; // Fiat cause problems with offer removal (don` found out why, but we want plain objects anyway)
private final long fiatPrice;
private final Coin amount; private final Coin amount;
private final Coin minAmount; private final Coin minAmount;
//TODO use hex string //TODO use hex string
@ -70,7 +71,7 @@ public class Offer implements Serializable {
public Offer(String id, public Offer(String id,
PublicKey messagePublicKey, PublicKey messagePublicKey,
Direction direction, Direction direction,
Fiat price, long fiatPrice,
Coin amount, Coin amount,
Coin minAmount, Coin minAmount,
BankAccountType bankAccountType, BankAccountType bankAccountType,
@ -84,7 +85,7 @@ public class Offer implements Serializable {
this.id = id; this.id = id;
this.messagePublicKey = messagePublicKey; this.messagePublicKey = messagePublicKey;
this.direction = direction; this.direction = direction;
this.price = price; this.fiatPrice = fiatPrice;
this.amount = amount; this.amount = amount;
this.minAmount = minAmount; this.minAmount = minAmount;
this.bankAccountType = bankAccountType; this.bankAccountType = bankAccountType;
@ -119,7 +120,7 @@ public class Offer implements Serializable {
} }
public Fiat getPrice() { public Fiat getPrice() {
return price; return Fiat.valueOf(currency.getCurrencyCode(), fiatPrice);
} }
public Coin getAmount() { public Coin getAmount() {
@ -155,9 +156,8 @@ public class Offer implements Serializable {
} }
public Fiat getVolumeByAmount(Coin amount) { public Fiat getVolumeByAmount(Coin amount) {
if (price != null && amount != null && !amount.isZero() && !price.isZero()) { if (fiatPrice != 0 && amount != null && !amount.isZero())
return new ExchangeRate(price).coinToFiat(amount); return new ExchangeRate(Fiat.valueOf(currency.getCurrencyCode(), fiatPrice)).coinToFiat(amount);
}
else else
return null; return null;
} }
@ -197,7 +197,7 @@ public class Offer implements Serializable {
"direction=" + direction + "direction=" + direction +
", currency=" + currency + ", currency=" + currency +
", uid='" + id + '\'' + ", uid='" + id + '\'' +
", price=" + price + ", fiatPrice=" + fiatPrice +
", amount=" + amount + ", amount=" + amount +
", minAmount=" + minAmount + ", minAmount=" + minAmount +
", messagePubKey=" + messagePublicKey.hashCode() + ", messagePubKey=" + messagePublicKey.hashCode() +

View file

@ -170,7 +170,7 @@ public class TradeManager {
Offer offer = new Offer(id, Offer offer = new Offer(id,
user.getMessagePublicKey(), user.getMessagePublicKey(),
direction, direction,
price, price.getValue(),
amount, amount,
minAmount, minAmount,
user.getCurrentBankAccount().getBankAccountType(), user.getCurrentBankAccount().getBankAccountType(),

View file

@ -18,6 +18,7 @@
package io.bitsquare.trade.protocol.createoffer.tasks; package io.bitsquare.trade.protocol.createoffer.tasks;
import io.bitsquare.msg.MessageFacade; import io.bitsquare.msg.MessageFacade;
import io.bitsquare.msg.listeners.AddOfferListener;
import io.bitsquare.trade.Offer; import io.bitsquare.trade.Offer;
import io.bitsquare.trade.handlers.FaultHandler; import io.bitsquare.trade.handlers.FaultHandler;
import io.bitsquare.trade.handlers.ResultHandler; import io.bitsquare.trade.handlers.ResultHandler;
@ -30,7 +31,7 @@ public class PublishOfferToDHT {
public static void run(ResultHandler resultHandler, FaultHandler faultHandler, MessageFacade messageFacade, public static void run(ResultHandler resultHandler, FaultHandler faultHandler, MessageFacade messageFacade,
Offer offer) { Offer offer) {
messageFacade.addOffer(offer, new MessageFacade.AddOfferListener() { messageFacade.addOffer(offer, new AddOfferListener() {
@Override @Override
public void onComplete() { public void onComplete() {
resultHandler.onResult(); resultHandler.onResult();