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 OrderBookListener orderBookListener;
private final ChangeListener<BankAccount> bankAccountChangeListener;
private final ChangeListener<Boolean> dirtyListener;
private final ChangeListener<Number> invalidationListener;
private String fiatCode;
private AnimationTimer pollingTimer;
private Country country;
@ -71,7 +71,11 @@ public class OrderBook {
this.user = user;
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() {
@Override
public void onOfferAdded(Offer offer) {
@ -87,7 +91,7 @@ public class OrderBook {
@Override
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() {
log.trace("addListeners ");
user.currentBankAccountProperty().addListener(bankAccountChangeListener);
messageFacade.addOrderBookListener(orderBookListener);
messageFacade.getIsDirtyProperty().addListener(dirtyListener);
messageFacade.invalidationTimestampProperty().addListener(invalidationListener);
}
private void removeListeners() {
log.trace("removeListeners ");
user.currentBankAccountProperty().removeListener(bankAccountChangeListener);
messageFacade.removeOrderBookListener(orderBookListener);
messageFacade.getIsDirtyProperty().removeListener(dirtyListener);
messageFacade.invalidationTimestampProperty().removeListener(invalidationListener);
}
private void addOfferToOrderBookListItems(Offer offer) {
@ -155,6 +161,7 @@ public class OrderBook {
}
private void requestOffers() {
log.debug("requestOffers");
messageFacade.getOffers(fiatCode);
}
@ -168,7 +175,7 @@ public class OrderBook {
addListeners();
setBankAccount(user.getCurrentBankAccount());
pollingTimer = Utilities.setInterval(1000, (animationTimer) -> {
messageFacade.getDirtyFlag(fiatCode);
messageFacade.getInvalidationTimeStamp(fiatCode);
return null;
});

View File

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

View File

@ -18,6 +18,7 @@
package io.bitsquare.msg;
import io.bitsquare.arbitrator.Arbitrator;
import io.bitsquare.msg.listeners.AddOfferListener;
import io.bitsquare.msg.listeners.ArbitratorListener;
import io.bitsquare.msg.listeners.BootstrapListener;
import io.bitsquare.msg.listeners.GetPeerAddressListener;
@ -44,8 +45,8 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import net.tomp2p.dht.FutureGet;
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.
*/
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 String ARBITRATORS_ROOT = "ArbitratorsRoot";
public P2PNode getP2pNode() {
return p2pNode;
}
private final P2PNode p2pNode;
private final User user;
private final List<OrderBookListener> orderBookListeners = new ArrayList<>();
private final List<ArbitratorListener> arbitratorListeners = new ArrayList<>();
private final List<IncomingTradeMessageListener> incomingTradeMessageListeners = new ArrayList<>();
private final User user;
private SeedNodeAddress.StaticSeedNodeAddresses defaultStaticSeedNodeAddresses;
private final LongProperty invalidationTimestamp = new SimpleLongProperty(0);
///////////////////////////////////////////////////////////////////////////////////////////
@ -174,13 +164,11 @@ public class MessageFacade implements MessageBroker {
try {
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
int defaultOfferTTL = 30 * 24 * 60 * 60;
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.addListener(new BaseFutureListener<BaseFuture>() {
@Override
@ -201,7 +189,7 @@ public class MessageFacade implements MessageBroker {
});
// 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 +
", value: " + offerData + "]");
});
@ -237,10 +225,8 @@ public class MessageFacade implements MessageBroker {
Number160 locationKey = Number160.createHash(offer.getCurrency().getCurrencyCode());
try {
final Data offerData = new Data(offer);
log.trace("Remove offer from DHT requested. Removed data: [locationKey: " + locationKey +
", value: " + offerData + "]");
", hash: " + offerData.hash().toString() + "]");
FutureRemove futureRemove = p2pNode.removeFromDataMap(locationKey, offerData);
futureRemove.addListener(new BaseFutureListener<BaseFuture>() {
@Override
@ -258,7 +244,7 @@ public class MessageFacade implements MessageBroker {
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 + ", " +
@ -283,6 +269,7 @@ public class MessageFacade implements MessageBroker {
public void getOffers(String currencyCode) {
Number160 locationKey = Number160.createHash(currencyCode);
log.trace("Get offers from DHT requested for locationKey: " + locationKey);
FutureGet futureGet = p2pNode.getDataMap(locationKey);
futureGet.addListener(new BaseFutureAdapter<BaseFuture>() {
@Override
@ -306,12 +293,19 @@ public class MessageFacade implements MessageBroker {
listener.onOffersReceived(offers)));
}
//log.trace("Get offers from DHT was successful");
/* log.trace("Get offers from DHT was successful. Stored data: [key: " + locationKey
+ ", values: " + futureGet.dataMap() + "]");*/
log.trace("Get offers from DHT was successful. Stored data: [key: " + locationKey
+ ", values: " + futureGet.dataMap() + "]");
}
else {
log.error("Get offers from DHT was not successful with reason:" + baseFuture.failedReason());
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 {
log.error("Get offers from DHT was not successful with reason:" + baseFuture.failedReason());
}
}
}
});
@ -442,83 +436,88 @@ public class MessageFacade implements MessageBroker {
public void removeIncomingTradeMessageListener(IncomingTradeMessageListener listener) {
incomingTradeMessageListeners.remove(listener);
}
/*
* 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 BooleanProperty getIsDirtyProperty() {
return isDirty;
public void updateInvalidationTimestamp(Number160 locationKey) {
invalidationTimestamp.set(System.currentTimeMillis());
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());
}
@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 void getDirtyFlag(String currencyCode) {
public LongProperty invalidationTimestampProperty() {
return invalidationTimestamp;
}
public void getInvalidationTimeStamp(String currencyCode) {
Number160 locationKey = Number160.createHash(currencyCode);
try {
FutureGet getFuture = p2pNode.getData(getDirtyLocationKey(locationKey));
FutureGet getFuture = p2pNode.getData(getInvalidatedLocationKey(locationKey));
getFuture.addListener(new BaseFutureListener<BaseFuture>() {
@Override
public void operationComplete(BaseFuture future) throws Exception {
Data data = getFuture.data();
if (data != null) {
Object object = data.object();
if (object instanceof Long) {
Platform.runLater(() -> onGetDirtyFlag((Long) object));
if (getFuture.isSuccess()) {
Data data = getFuture.data();
if (data != null && data.object() instanceof Long) {
final Object object = data.object();
Platform.runLater(() -> {
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
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) {
log.error("Get invalidationTimestamp from DHT failed with exception:" + e.getMessage());
e.printStackTrace();
}
}
private Long lastTimeStamp = -3L;
private final BooleanProperty isDirty = new SimpleBooleanProperty(false);
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");
private Number160 getInvalidatedLocationKey(Number160 locationKey) {
return Number160.createHash(locationKey + "invalidated");
}
@ -529,7 +528,6 @@ public class MessageFacade implements MessageBroker {
@Override
public void handleMessage(Object message, PeerAddress peerAddress) {
if (message instanceof TradeMessage) {
log.error("####################");
Platform.runLater(() -> incomingTradeMessageListeners.stream().forEach(e ->
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 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 minAmount;
//TODO use hex string
@ -70,7 +71,7 @@ public class Offer implements Serializable {
public Offer(String id,
PublicKey messagePublicKey,
Direction direction,
Fiat price,
long fiatPrice,
Coin amount,
Coin minAmount,
BankAccountType bankAccountType,
@ -84,7 +85,7 @@ public class Offer implements Serializable {
this.id = id;
this.messagePublicKey = messagePublicKey;
this.direction = direction;
this.price = price;
this.fiatPrice = fiatPrice;
this.amount = amount;
this.minAmount = minAmount;
this.bankAccountType = bankAccountType;
@ -119,7 +120,7 @@ public class Offer implements Serializable {
}
public Fiat getPrice() {
return price;
return Fiat.valueOf(currency.getCurrencyCode(), fiatPrice);
}
public Coin getAmount() {
@ -155,9 +156,8 @@ public class Offer implements Serializable {
}
public Fiat getVolumeByAmount(Coin amount) {
if (price != null && amount != null && !amount.isZero() && !price.isZero()) {
return new ExchangeRate(price).coinToFiat(amount);
}
if (fiatPrice != 0 && amount != null && !amount.isZero())
return new ExchangeRate(Fiat.valueOf(currency.getCurrencyCode(), fiatPrice)).coinToFiat(amount);
else
return null;
}
@ -197,7 +197,7 @@ public class Offer implements Serializable {
"direction=" + direction +
", currency=" + currency +
", uid='" + id + '\'' +
", price=" + price +
", fiatPrice=" + fiatPrice +
", amount=" + amount +
", minAmount=" + minAmount +
", messagePubKey=" + messagePublicKey.hashCode() +

View File

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

View File

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