Merge branch 'Development'

Conflicts:
	doc/build.md
This commit is contained in:
Manfred Karrer 2016-07-27 14:56:53 +02:00
commit 13c2db81d7
166 changed files with 4564 additions and 965 deletions

View file

@ -5,7 +5,7 @@
<parent>
<artifactId>parent</artifactId>
<groupId>io.bitsquare</groupId>
<version>0.4.9</version>
<version>0.4.9.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View file

@ -0,0 +1,35 @@
package io.bitsquare.app;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
public class Capabilities {
private static final Logger log = LoggerFactory.getLogger(Capabilities.class);
// We can define here special features the client is supporting.
// Useful for updates to new versions where a new data type would break backwards compatibility or to
// limit a node to certain behaviour and roles like the seed nodes.
// We don't use the Enum in any serialized data, as changes in the enum would break backwards compatibility. We use the ordinal integer instead.
// Sequence in the enum must not be changed (append only).
public enum Capability {
TRADE_STATISTICS
}
public static void setCapabilities(ArrayList<Integer> capabilities) {
Capabilities.capabilities = capabilities;
}
private static ArrayList<Integer> capabilities = new ArrayList<>(Arrays.asList(
Capability.TRADE_STATISTICS.ordinal()
));
/**
* @return The Capabilities as ordinal integer the client supports.
*/
public static ArrayList<Integer> getCapabilities() {
return capabilities;
}
}

View file

@ -34,6 +34,10 @@ public class Log {
private static SizeBasedTriggeringPolicy triggeringPolicy;
private static Logger logbackLogger;
public static void setLevel(Level logLevel) {
logbackLogger.setLevel(logLevel);
}
public static void setup(String fileName) {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
@ -84,10 +88,6 @@ public class Log {
logbackLogger.addAppender(errorAppender);*/
}
public static void setLevel(Level logLevel) {
logbackLogger.setLevel(logLevel);
}
public static void traceCall() {
if (LoggerFactory.getLogger(Log.class).isTraceEnabled()) {
StackTraceElement stackTraceElement = new Throwable().getStackTrace()[1];

View file

@ -24,7 +24,7 @@ public class Version {
private static final Logger log = LoggerFactory.getLogger(Version.class);
// The application versions
public static final String VERSION = "0.4.9";
public static final String VERSION = "0.4.9.1";
// The version nr. for the objects sent over the network. A change will break the serialization of old objects.
// If objects are used for both network and database the network version is applied.
@ -39,14 +39,13 @@ public class Version {
// VERSION = 0.3.5 -> LOCAL_DB_VERSION = 2
// VERSION = 0.4.0 -> LOCAL_DB_VERSION = 3
// VERSION = 0.4.2 -> LOCAL_DB_VERSION = 4
public static final int LOCAL_DB_VERSION = 4;
public static final int LOCAL_DB_VERSION = 4;
// The version nr. of the current protocol. The offer holds that version.
// A taker will check the version of the offers to see if his version is compatible.
public static final int TRADE_PROTOCOL_VERSION = 1;
private static int p2pMessageVersion;
public static int getP2PMessageVersion() {
// TODO investigate why a changed NETWORK_PROTOCOL_VERSION for the serialized objects does not trigger
// reliable a disconnect., but java serialisation should be replaced anyway, so using one existing field

View file

@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit;
public class Clock {
private static final Logger log = LoggerFactory.getLogger(Clock.class);
public static final int IDLE_TOLERANCE = 5000;
public static final int IDLE_TOLERANCE = 20000;
public interface Listener {
void onSecondTick();

View file

@ -0,0 +1,5 @@
package io.bitsquare.common;
public class CommonOptionKeys {
public static final String LOG_LEVEL_KEY = "logLevel";
}

View file

@ -1,6 +0,0 @@
package io.bitsquare.common;
public class OptionKeys {
public static final String LOG_LEVEL_KEY = "logLevel";
public static final String IGNORE_DEV_MSG_KEY = "ignoreDevMsg";
}

View file

@ -52,7 +52,10 @@ public class RestartUtil {
try {
final String command = "nohup " + cmd.toString() + " >/dev/null 2>" + logPath + " &";
log.warn("Executing cmd for restart:\n" + command);
log.warn("\n\n############################################################\n" +
"Executing cmd for restart: {}" +
"\n############################################################\n\n",
command);
Runtime.getRuntime().exec(command);
} catch (IOException e) {
e.printStackTrace();

View file

@ -25,6 +25,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.*;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -465,4 +466,12 @@ public class Utilities {
// This simply matches the Oracle JRE, but not OpenJDK.
return "Java(TM) SE Runtime Environment".equals(System.getProperty("java.runtime.name"));
}
public static String toTruncatedString(Object message, int maxLenght) {
return StringUtils.abbreviate(message.toString(), maxLenght).replace("\n", "");
}
public static String toTruncatedString(Object message) {
return toTruncatedString(message, 200);
}
}

View file

@ -77,7 +77,7 @@ public class Storage<T extends Serializable> {
}
@Nullable
public T initAndGetPersisted(String fileName) {
public T initAndGetPersistedWithFileName(String fileName) {
this.fileName = fileName;
storageFile = new File(dir, fileName);
fileManager = new FileManager<>(dir, storageFile, 300);

View file

@ -6,7 +6,7 @@
<parent>
<artifactId>parent</artifactId>
<groupId>io.bitsquare</groupId>
<version>0.4.9</version>
<version>0.4.9.1</version>
</parent>
<artifactId>core</artifactId>

View file

@ -19,7 +19,7 @@ package io.bitsquare.alert;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.bitsquare.common.OptionKeys;
import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.storage.HashMapChangedListener;
@ -56,7 +56,7 @@ public class AlertManager {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public AlertManager(P2PService p2PService, KeyRing keyRing, User user, @Named(OptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
public AlertManager(P2PService p2PService, KeyRing keyRing, User user, @Named(CoreOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
this.p2PService = p2PService;
this.keyRing = keyRing;
this.user = user;

View file

@ -19,10 +19,13 @@ package io.bitsquare.alert;
import com.google.inject.Singleton;
import io.bitsquare.app.AppModule;
import io.bitsquare.app.CoreOptionKeys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import static com.google.inject.name.Names.named;
public class AlertModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(AlertModule.class);
@ -34,5 +37,6 @@ public class AlertModule extends AppModule {
protected final void configure() {
bind(AlertManager.class).in(Singleton.class);
bind(PrivateNotificationManager.class).in(Singleton.class);
bindConstant().annotatedWith(named(CoreOptionKeys.IGNORE_DEV_MSG_KEY)).to(env.getRequiredProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY));
}
}

View file

@ -19,7 +19,7 @@ package io.bitsquare.alert;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.bitsquare.common.OptionKeys;
import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.crypto.DecryptedMsgWithPubKey;
import io.bitsquare.p2p.Message;
@ -58,7 +58,7 @@ public class PrivateNotificationManager {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public PrivateNotificationManager(P2PService p2PService, KeyRing keyRing, @Named(OptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
public PrivateNotificationManager(P2PService p2PService, KeyRing keyRing, @Named(CoreOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
this.p2PService = p2PService;
this.keyRing = keyRing;

View file

@ -20,11 +20,12 @@ package io.bitsquare.app;
import ch.qos.logback.classic.Level;
import io.bitsquare.BitsquareException;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.BtcOptionKeys;
import io.bitsquare.btc.UserAgent;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.crypto.KeyStorage;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.network.OptionKeys;
import io.bitsquare.network.NetworkOptionKeys;
import io.bitsquare.storage.Storage;
import io.bitsquare.util.spring.JOptCommandLinePropertySource;
import joptsimple.OptionSet;
@ -51,11 +52,6 @@ public class BitsquareEnvironment extends StandardEnvironment {
private static final Logger log = LoggerFactory.getLogger(BitsquareEnvironment.class);
private static final String BITCOIN_NETWORK_PROP = "bitcoinNetwork.properties";
public static final String USER_DATA_DIR_KEY = "userDataDir";
public static final String DEFAULT_USER_DATA_DIR = defaultUserDataDir();
public static final String APP_NAME_KEY = "appName";
public static void setDefaultAppName(String defaultAppName) {
DEFAULT_APP_NAME = defaultAppName;
@ -63,7 +59,7 @@ public class BitsquareEnvironment extends StandardEnvironment {
public static String DEFAULT_APP_NAME = "Bitsquare";
public static final String APP_DATA_DIR_KEY = "appDataDir";
public static final String DEFAULT_USER_DATA_DIR = defaultUserDataDir();
public static final String DEFAULT_APP_DATA_DIR = appDataDir(DEFAULT_USER_DATA_DIR, DEFAULT_APP_NAME);
public static final String LOG_LEVEL_DEFAULT = (DevFlags.STRESS_TEST_MODE || DevFlags.DEV_MODE) ? Level.TRACE.levelStr : Level.WARN.levelStr;
@ -78,13 +74,12 @@ public class BitsquareEnvironment extends StandardEnvironment {
private final String appName;
private final String userDataDir;
private final String appDataDir;
private final String btcNetworkDir;
private final String logLevel;
private BitcoinNetwork bitcoinNetwork;
private final String seedNodes, ignoreDevMsg;
private final String myAddress, banList;
private final String btcSeedNodes, seedNodes, ignoreDevMsg, useTorForBtc, useTorForHttp,
myAddress, banList, dumpStatistics, socks5ProxyBtcAddress, socks5ProxyHttpAddress;
public BitsquareEnvironment(OptionSet options) {
this(new JOptCommandLinePropertySource(BITSQUARE_COMMANDLINE_PROPERTY_SOURCE_NAME, checkNotNull(
@ -108,7 +103,7 @@ public class BitsquareEnvironment extends StandardEnvironment {
log.warn("propertiesObject not instance of Properties");
}
}
properties.setProperty(BitcoinNetwork.KEY, bitcoinNetwork.name());
properties.setProperty(BtcOptionKeys.BTC_NETWORK, bitcoinNetwork.name());
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
properties.store(fileOutputStream, null);
@ -125,35 +120,54 @@ public class BitsquareEnvironment extends StandardEnvironment {
}
protected BitsquareEnvironment(PropertySource commandLineProperties) {
userDataDir = commandLineProperties.containsProperty(USER_DATA_DIR_KEY) ?
(String) commandLineProperties.getProperty(USER_DATA_DIR_KEY) :
DEFAULT_USER_DATA_DIR;
appName = commandLineProperties.containsProperty(APP_NAME_KEY) ?
(String) commandLineProperties.getProperty(APP_NAME_KEY) :
DEFAULT_APP_NAME;
appDataDir = commandLineProperties.containsProperty(APP_DATA_DIR_KEY) ?
(String) commandLineProperties.getProperty(APP_DATA_DIR_KEY) :
appDataDir(userDataDir, appName);
logLevel = commandLineProperties.containsProperty(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY) ?
(String) commandLineProperties.getProperty(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY) :
logLevel = commandLineProperties.containsProperty(CommonOptionKeys.LOG_LEVEL_KEY) ?
(String) commandLineProperties.getProperty(CommonOptionKeys.LOG_LEVEL_KEY) :
LOG_LEVEL_DEFAULT;
seedNodes = commandLineProperties.containsProperty(OptionKeys.SEED_NODES_KEY) ?
(String) commandLineProperties.getProperty(OptionKeys.SEED_NODES_KEY) :
userDataDir = commandLineProperties.containsProperty(CoreOptionKeys.USER_DATA_DIR_KEY) ?
(String) commandLineProperties.getProperty(CoreOptionKeys.USER_DATA_DIR_KEY) :
DEFAULT_USER_DATA_DIR;
appName = commandLineProperties.containsProperty(CoreOptionKeys.APP_NAME_KEY) ?
(String) commandLineProperties.getProperty(CoreOptionKeys.APP_NAME_KEY) :
DEFAULT_APP_NAME;
appDataDir = commandLineProperties.containsProperty(CoreOptionKeys.APP_DATA_DIR_KEY) ?
(String) commandLineProperties.getProperty(CoreOptionKeys.APP_DATA_DIR_KEY) :
appDataDir(userDataDir, appName);
ignoreDevMsg = commandLineProperties.containsProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY) ?
(String) commandLineProperties.getProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY) :
"";
dumpStatistics = commandLineProperties.containsProperty(CoreOptionKeys.DUMP_STATISTICS) ?
(String) commandLineProperties.getProperty(CoreOptionKeys.DUMP_STATISTICS) :
"";
myAddress = commandLineProperties.containsProperty(OptionKeys.MY_ADDRESS) ?
(String) commandLineProperties.getProperty(OptionKeys.MY_ADDRESS) :
"";
banList = commandLineProperties.containsProperty(OptionKeys.BAN_LIST) ?
(String) commandLineProperties.getProperty(OptionKeys.BAN_LIST) :
seedNodes = commandLineProperties.containsProperty(NetworkOptionKeys.SEED_NODES_KEY) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.SEED_NODES_KEY) :
"";
ignoreDevMsg = commandLineProperties.containsProperty(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY) ?
(String) commandLineProperties.getProperty(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY) :
myAddress = commandLineProperties.containsProperty(NetworkOptionKeys.MY_ADDRESS) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.MY_ADDRESS) :
"";
banList = commandLineProperties.containsProperty(NetworkOptionKeys.BAN_LIST) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.BAN_LIST) :
"";
socks5ProxyBtcAddress = commandLineProperties.containsProperty(NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS) :
"";
socks5ProxyHttpAddress = commandLineProperties.containsProperty(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS) :
"";
useTorForHttp = commandLineProperties.containsProperty(NetworkOptionKeys.USE_TOR_FOR_HTTP) ?
(String) commandLineProperties.getProperty(NetworkOptionKeys.USE_TOR_FOR_HTTP) :
"";
btcSeedNodes = commandLineProperties.containsProperty(BtcOptionKeys.BTC_SEED_NODES) ?
(String) commandLineProperties.getProperty(BtcOptionKeys.BTC_SEED_NODES) :
"";
useTorForBtc = commandLineProperties.containsProperty(BtcOptionKeys.USE_TOR_FOR_BTC) ?
(String) commandLineProperties.getProperty(BtcOptionKeys.USE_TOR_FOR_BTC) :
"";
MutablePropertySources propertySources = this.getPropertySources();
@ -163,7 +177,7 @@ public class BitsquareEnvironment extends StandardEnvironment {
propertySources.addLast(homeDirProperties());
propertySources.addLast(classpathProperties());
bitcoinNetwork = BitcoinNetwork.valueOf(getProperty(BitcoinNetwork.KEY, BitcoinNetwork.DEFAULT.name()).toUpperCase());
bitcoinNetwork = BitcoinNetwork.valueOf(getProperty(BtcOptionKeys.BTC_NETWORK, BitcoinNetwork.DEFAULT.name()).toUpperCase());
btcNetworkDir = Paths.get(appDataDir, bitcoinNetwork.name().toLowerCase()).toString();
File btcNetworkDirFile = new File(btcNetworkDir);
if (!btcNetworkDirFile.exists())
@ -210,27 +224,31 @@ public class BitsquareEnvironment extends StandardEnvironment {
private static final long serialVersionUID = -8478089705207326165L;
{
setProperty(APP_DATA_DIR_KEY, appDataDir);
setProperty(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY, logLevel);
setProperty(CommonOptionKeys.LOG_LEVEL_KEY, logLevel);
setProperty(OptionKeys.SEED_NODES_KEY, seedNodes);
setProperty(OptionKeys.MY_ADDRESS, myAddress);
setProperty(OptionKeys.BAN_LIST, banList);
setProperty(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg);
setProperty(NetworkOptionKeys.SEED_NODES_KEY, seedNodes);
setProperty(NetworkOptionKeys.MY_ADDRESS, myAddress);
setProperty(NetworkOptionKeys.BAN_LIST, banList);
setProperty(NetworkOptionKeys.TOR_DIR, Paths.get(btcNetworkDir, "tor").toString());
setProperty(NetworkOptionKeys.NETWORK_ID, String.valueOf(bitcoinNetwork.ordinal()));
setProperty(NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS, socks5ProxyBtcAddress);
setProperty(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS, socks5ProxyHttpAddress);
setProperty(NetworkOptionKeys.USE_TOR_FOR_HTTP, useTorForHttp);
setProperty(APP_NAME_KEY, appName);
setProperty(USER_DATA_DIR_KEY, userDataDir);
setProperty(CoreOptionKeys.APP_DATA_DIR_KEY, appDataDir);
setProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY, ignoreDevMsg);
setProperty(CoreOptionKeys.DUMP_STATISTICS, dumpStatistics);
setProperty(CoreOptionKeys.APP_NAME_KEY, appName);
setProperty(CoreOptionKeys.USER_DATA_DIR_KEY, userDataDir);
setProperty(BtcOptionKeys.BTC_SEED_NODES, btcSeedNodes);
setProperty(BtcOptionKeys.USE_TOR_FOR_BTC, useTorForBtc);
setProperty(UserAgent.NAME_KEY, appName);
setProperty(UserAgent.VERSION_KEY, Version.VERSION);
setProperty(WalletService.DIR_KEY, btcNetworkDir);
setProperty(BtcOptionKeys.WALLET_DIR, btcNetworkDir);
setProperty(Storage.DIR_KEY, Paths.get(btcNetworkDir, "db").toString());
setProperty(KeyStorage.DIR_KEY, Paths.get(btcNetworkDir, "keys").toString());
setProperty(OptionKeys.TOR_DIR, Paths.get(btcNetworkDir, "tor").toString());
setProperty(OptionKeys.NETWORK_ID, String.valueOf(bitcoinNetwork.ordinal()));
}
});
}

View file

@ -19,8 +19,10 @@ package io.bitsquare.app;
import io.bitsquare.BitsquareException;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.BtcOptionKeys;
import io.bitsquare.btc.RegTestHost;
import io.bitsquare.network.OptionKeys;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.network.NetworkOptionKeys;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.util.joptsimple.EnumValueConverter;
import joptsimple.OptionException;
@ -68,46 +70,58 @@ public abstract class BitsquareExecutable {
}
protected void customizeOptionParsing(OptionParser parser) {
parser.accepts(USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
.withRequiredArg();
parser.accepts(APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME))
.withRequiredArg();
parser.accepts(APP_DATA_DIR_KEY, description("Application data directory", DEFAULT_APP_DATA_DIR))
.withRequiredArg();
parser.accepts(io.bitsquare.common.OptionKeys.LOG_LEVEL_KEY, description("Log level [OFF, ALL, ERROR, WARN, INFO, DEBUG, TRACE]", LOG_LEVEL_DEFAULT))
parser.accepts(CommonOptionKeys.LOG_LEVEL_KEY, description("Log level [OFF, ALL, ERROR, WARN, INFO, DEBUG, TRACE]", LOG_LEVEL_DEFAULT))
.withRequiredArg();
parser.accepts(OptionKeys.SEED_NODES_KEY, description("Override hard coded seed nodes as comma separated list: E.g. rxdkppp3vicnbgqt.onion:8002, mfla72c4igh5ta2t.onion:8002", ""))
parser.accepts(NetworkOptionKeys.SEED_NODES_KEY, description("Override hard coded seed nodes as comma separated list: E.g. rxdkppp3vicnbgqt.onion:8002, mfla72c4igh5ta2t.onion:8002", ""))
.withRequiredArg();
parser.accepts(OptionKeys.MY_ADDRESS, description("My own onion address (used for botstrap nodes to exclude itself)", ""))
parser.accepts(NetworkOptionKeys.MY_ADDRESS, description("My own onion address (used for botstrap nodes to exclude itself)", ""))
.withRequiredArg();
parser.accepts(OptionKeys.BAN_LIST, description("Nodes to exclude from network connections.", ""))
parser.accepts(NetworkOptionKeys.BAN_LIST, description("Nodes to exclude from network connections.", ""))
.withRequiredArg();
parser.accepts(io.bitsquare.common.OptionKeys.IGNORE_DEV_MSG_KEY, description("If set to true all signed messages from Bitsquare developers are ignored " +
// use a fixed port as arbitrator use that for his ID
parser.accepts(NetworkOptionKeys.PORT_KEY, description("Port to listen on", 9999))
.withRequiredArg()
.ofType(int.class);
parser.accepts(NetworkOptionKeys.USE_LOCALHOST, description("Use localhost network for development", false))
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(NetworkOptionKeys.MAX_CONNECTIONS, description("Max. connections a peer will try to keep", P2PService.MAX_CONNECTIONS_DEFAULT))
.withRequiredArg()
.ofType(int.class);
parser.accepts(NetworkOptionKeys.SOCKS_5_PROXY_BTC_ADDRESS, description("A proxy address to be used for Bitcoin network. [host:port]", ""))
.withRequiredArg();
parser.accepts(NetworkOptionKeys.SOCKS_5_PROXY_HTTP_ADDRESS, description("A proxy address to be used for Http requests (should be non-Tor). [host:port]", ""))
.withRequiredArg();
parser.accepts(NetworkOptionKeys.USE_TOR_FOR_HTTP, description("If set to true all http traffic (expect Poloniex) is routed over tor (socks 5 proxy)", ""))
.withRequiredArg();
parser.accepts(CoreOptionKeys.USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
.withRequiredArg();
parser.accepts(CoreOptionKeys.APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME))
.withRequiredArg();
parser.accepts(CoreOptionKeys.APP_DATA_DIR_KEY, description("Application data directory", DEFAULT_APP_DATA_DIR))
.withRequiredArg();
parser.accepts(CoreOptionKeys.IGNORE_DEV_MSG_KEY, description("If set to true all signed messages from Bitsquare developers are ignored " +
"(Global alert, Version update alert, Filters for offers, nodes or payment account data)", false))
.withRequiredArg()
.ofType(boolean.class);
// use a fixed port as arbitrator use that for his ID
parser.accepts(OptionKeys.PORT_KEY, description("Port to listen on", 9999))
.withRequiredArg()
.ofType(int.class);
parser.accepts(OptionKeys.USE_LOCALHOST, description("Use localhost network for development", false))
parser.accepts(CoreOptionKeys.DUMP_STATISTICS, description("If set to true the trade statistics are stored as json file in the data dir.", false))
.withRequiredArg()
.ofType(boolean.class);
parser.accepts(OptionKeys.MAX_CONNECTIONS, description("Max. connections a peer will try to keep", P2PService.MAX_CONNECTIONS_DEFAULT))
.withRequiredArg()
.ofType(int.class);
parser.accepts(BitcoinNetwork.KEY, description("Bitcoin network", BitcoinNetwork.DEFAULT))
parser.accepts(BtcOptionKeys.BTC_NETWORK, description("Bitcoin network", BitcoinNetwork.DEFAULT))
.withRequiredArg()
.ofType(BitcoinNetwork.class)
.withValuesConvertedBy(new EnumValueConverter(BitcoinNetwork.class));
parser.accepts(RegTestHost.KEY, description("", RegTestHost.DEFAULT))
parser.accepts(BtcOptionKeys.REG_TEST_HOST, description("", RegTestHost.DEFAULT))
.withRequiredArg()
.ofType(RegTestHost.class)
.withValuesConvertedBy(new EnumValueConverter(RegTestHost.class));
parser.accepts(BtcOptionKeys.BTC_SEED_NODES, description("Custom seed nodes used for BitcoinJ.", ""))
.withRequiredArg();
parser.accepts(BtcOptionKeys.USE_TOR_FOR_BTC, description("If set to true BitcoinJ is routed over tor (socks 5 proxy).", ""))
.withRequiredArg();
}
protected static String description(String descText, Object defaultValue) {

View file

@ -0,0 +1,9 @@
package io.bitsquare.app;
public class CoreOptionKeys {
public static final String IGNORE_DEV_MSG_KEY = "ignoreDevMsg";
public static final String DUMP_STATISTICS = "dumpStatistics";
public static final String USER_DATA_DIR_KEY = "userDataDir";
public static final String APP_NAME_KEY = "appName";
public static final String APP_DATA_DIR_KEY = "appDataDir";
}

View file

@ -23,6 +23,7 @@ import io.bitsquare.p2p.messaging.MailboxMessage;
import java.util.UUID;
public abstract class DisputeMessage implements MailboxMessage {
//TODO add serialVersionUID also in superclasses as changes would break compatibility
private final int messageVersion = Version.getP2PMessageVersion();
private final String uid = UUID.randomUUID().toString();

View file

@ -20,7 +20,12 @@ package io.bitsquare.btc;
import com.google.inject.Singleton;
import io.bitsquare.app.AppModule;
import io.bitsquare.btc.blockchain.BlockchainService;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.blockchain.providers.BlockTrailProvider;
import io.bitsquare.btc.blockchain.providers.BlockrIOProvider;
import io.bitsquare.btc.blockchain.providers.TradeBlockProvider;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.btc.pricefeed.providers.BitcoinAveragePriceProvider;
import io.bitsquare.btc.pricefeed.providers.PoloniexPriceProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
@ -38,21 +43,31 @@ public class BitcoinModule extends AppModule {
@Override
protected void configure() {
bind(RegTestHost.class).toInstance(env.getProperty(RegTestHost.KEY, RegTestHost.class, RegTestHost.DEFAULT));
bind(RegTestHost.class).toInstance(env.getProperty(BtcOptionKeys.REG_TEST_HOST, RegTestHost.class, RegTestHost.DEFAULT));
bind(FeePolicy.class).in(Singleton.class);
bindConstant().annotatedWith(named(UserAgent.NAME_KEY)).to(env.getRequiredProperty(UserAgent.NAME_KEY));
bindConstant().annotatedWith(named(UserAgent.VERSION_KEY)).to(env.getRequiredProperty(UserAgent.VERSION_KEY));
bind(UserAgent.class).in(Singleton.class);
File walletDir = new File(env.getRequiredProperty(WalletService.DIR_KEY));
bind(File.class).annotatedWith(named(WalletService.DIR_KEY)).toInstance(walletDir);
File walletDir = new File(env.getRequiredProperty(BtcOptionKeys.WALLET_DIR));
bind(File.class).annotatedWith(named(BtcOptionKeys.WALLET_DIR)).toInstance(walletDir);
bindConstant().annotatedWith(named(BtcOptionKeys.BTC_SEED_NODES)).to(env.getRequiredProperty(BtcOptionKeys.BTC_SEED_NODES));
bindConstant().annotatedWith(named(BtcOptionKeys.USE_TOR_FOR_BTC)).to(env.getRequiredProperty(BtcOptionKeys.USE_TOR_FOR_BTC));
bind(AddressEntryList.class).in(Singleton.class);
bind(TradeWalletService.class).in(Singleton.class);
bind(WalletService.class).in(Singleton.class);
bind(BlockchainService.class).in(Singleton.class);
bind(PriceFeed.class).in(Singleton.class);
bind(PriceFeedService.class).in(Singleton.class);
bind(BitcoinAveragePriceProvider.class).in(Singleton.class);
bind(PoloniexPriceProvider.class).in(Singleton.class);
bind(BlockrIOProvider.class).in(Singleton.class);
bind(BlockTrailProvider.class).in(Singleton.class);
bind(TradeBlockProvider.class).in(Singleton.class);
}
}

View file

@ -27,7 +27,6 @@ public enum BitcoinNetwork {
TESTNET(TestNet3Params.get()),
REGTEST(RegTestParams.get());
public static final String KEY = "bitcoinNetwork";
public static final BitcoinNetwork DEFAULT = MAINNET;
private final NetworkParameters parameters;

View file

@ -0,0 +1,9 @@
package io.bitsquare.btc;
public class BtcOptionKeys {
public static final String BTC_NETWORK = "bitcoinNetwork";
public static final String REG_TEST_HOST = "bitcoinRegtestHost";
public static final String BTC_SEED_NODES = "btcSeedNodes";
public static final String USE_TOR_FOR_BTC = "useTorForBtc";
public static final String WALLET_DIR = "walletDir";
}

View file

@ -0,0 +1,28 @@
package io.bitsquare.btc;
import io.bitsquare.http.HttpClient;
import io.bitsquare.user.Preferences;
import java.io.Serializable;
public abstract class HttpClientProvider implements Serializable {
protected final HttpClient httpClient;
public HttpClientProvider(HttpClient httpClient, Preferences preferences, String baseUrl) {
this(httpClient, preferences, baseUrl, false);
}
public HttpClientProvider(HttpClient httpClient, Preferences preferences, String baseUrl, boolean ignoreSocks5Proxy) {
this.httpClient = httpClient;
httpClient.setBaseUrl(baseUrl);
httpClient.setIgnoreSocks5Proxy(ignoreSocks5Proxy || !preferences.getUseTorForHttpRequests());
if (!ignoreSocks5Proxy) {
preferences.useTorForHttpRequestsProperty().addListener((observable, oldValue, newValue) -> {
httpClient.setIgnoreSocks5Proxy(!newValue);
});
}
}
}

View file

@ -0,0 +1,94 @@
/**
* Copyright (C) 2010-2014 Leon Blakey <lord.quackstar at gmail.com>
*
* This file is part of PircBotX.
*
* PircBotX 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, either version 3 of the License, or (at your option) any later
* version.
*
* PircBotX 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
* PircBotX. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.btc;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.net.SocketFactory;
/**
* A basic SocketFactory for creating sockets that connect through the specified
* proxy.
*
* @author Leon Blakey
*/
public class ProxySocketFactory extends SocketFactory {
protected final Proxy proxy;
/**
* Create all sockets with the specified proxy.
*
* @param proxy An existing proxy
*/
public ProxySocketFactory(Proxy proxy) {
this.proxy = proxy;
}
/**
* A convenience constructor for creating a proxy with the specified host
* and port.
*
* @param proxyType The type of proxy were connecting to
* @param hostname The hostname of the proxy server
* @param port The port of the proxy server
*/
public ProxySocketFactory(Proxy.Type proxyType, String hostname, int port) {
this.proxy = new Proxy(proxyType, new InetSocketAddress(hostname, port));
}
@Override
public Socket createSocket() throws IOException {
Socket socket = new Socket(proxy);
return socket;
}
@Override
public Socket createSocket(String string, int i) throws IOException, UnknownHostException {
Socket socket = new Socket(proxy);
socket.connect(new InetSocketAddress(string, i));
return socket;
}
@Override
public Socket createSocket(String string, int i, InetAddress localAddress, int localPort) throws IOException, UnknownHostException {
Socket socket = new Socket(proxy);
socket.bind(new InetSocketAddress(localAddress, localPort));
socket.connect(new InetSocketAddress(string, i));
return socket;
}
@Override
public Socket createSocket(InetAddress ia, int i) throws IOException {
Socket socket = new Socket(proxy);
socket.connect(new InetSocketAddress(ia, i));
return socket;
}
@Override
public Socket createSocket(InetAddress ia, int i, InetAddress localAddress, int localPort) throws IOException {
Socket socket = new Socket(proxy);
socket.bind(new InetSocketAddress(localAddress, localPort));
socket.connect(new InetSocketAddress(ia, i));
return socket;
}
}

View file

@ -22,7 +22,6 @@ public enum RegTestHost {
LOCALHOST,
REG_TEST_SERVER; // 188.226.179.109
public static final String KEY = "bitcoinRegtestHost";
public static final RegTestHost DEFAULT = LOCALHOST;
public static final String SERVER_IP = "188.226.179.109";

View file

@ -0,0 +1,176 @@
/**
* Copyright 2011 Micheal Swiggs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.bitsquare.btc;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.net.discovery.PeerDiscovery;
import org.bitcoinj.net.discovery.PeerDiscoveryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;
/**
* SeedPeersSocks5Dns resolves peers via Proxy (Socks5) remote DNS.
*/
public class SeedPeersSocks5Dns implements PeerDiscovery {
private Socks5Proxy proxy;
private NetworkParameters params;
private InetSocketAddress[] seedAddrs;
private InetSocketAddress[] seedAddrsIP;
private int pnseedIndex;
private final InetSocketAddress[] seedAddrsResolved;
private static final Logger log = LoggerFactory.getLogger(SeedPeersSocks5Dns.class);
/**
* Supports finding peers by hostname over a socks5 proxy.
*
* @param Socks5Proxy proxy the socks5 proxy to connect over.
* @param NetworkParameters param to be used for seed and port information.
*/
public SeedPeersSocks5Dns(Socks5Proxy proxy, NetworkParameters params) {
this.proxy = proxy;
this.params = params;
this.seedAddrs = convertAddrsString( params.getDnsSeeds(), params.getPort() );
if( false ) {
// This is an example of how .onion servers could be used. Unfortunately there is presently no way
// to hand the onion address (or a connected socket) back to bitcoinj without it crashing in PeerAddress.
// note: the onion addresses should be added into bitcoinj NetworkParameters classes, eg for mainnet, testnet
// not here!
this.seedAddrs = new InetSocketAddress[] { InetSocketAddress.createUnresolved( "cajrifqkvalh2ooa.onion", 8333 ),
InetSocketAddress.createUnresolved( "bk7yp6epnmcllq72.onion", 8333 )
};
}
seedAddrsResolved = new InetSocketAddress[seedAddrs.length];
for(int idx = seedAddrs.length; idx < seedAddrsResolved.length; idx ++) {
seedAddrsResolved[idx] = seedAddrsIP[idx-seedAddrs.length];
}
}
/**
* Acts as an iterator, returning the address of each node in the list sequentially.
* Once all the list has been iterated, null will be returned for each subsequent query.
*
* @return InetSocketAddress - The address/port of the next node.
* @throws PeerDiscoveryException
*/
@Nullable
public InetSocketAddress getPeer() throws PeerDiscoveryException {
try {
return nextPeer();
} catch (UnknownHostException e) {
throw new PeerDiscoveryException(e);
}
}
/**
* worker for getPeer()
*/
@Nullable
private InetSocketAddress nextPeer() throws UnknownHostException, PeerDiscoveryException {
if (seedAddrs == null || seedAddrs.length == 0) {
throw new PeerDiscoveryException("No IP address seeds configured; unable to find any peers");
}
if (pnseedIndex >= seedAddrsResolved.length) {
return null;
}
if( seedAddrsResolved[pnseedIndex] == null ) {
seedAddrsResolved[pnseedIndex] = lookup( proxy, seedAddrs[pnseedIndex] );
}
log.error("SeedPeersSocks5Dns::nextPeer: " + seedAddrsResolved[pnseedIndex]);
return seedAddrsResolved[pnseedIndex++];
}
/**
* Returns an array containing all the Bitcoin nodes within the list.
*/
@Override
public InetSocketAddress[] getPeers(long timeoutValue, TimeUnit timeoutUnit) throws PeerDiscoveryException {
try {
return allPeers();
} catch (UnknownHostException e) {
throw new PeerDiscoveryException(e);
}
}
/**
* returns all seed peers, performs hostname lookups if necessary.
*/
private InetSocketAddress[] allPeers() throws UnknownHostException {
for (int i = 0; i < seedAddrsResolved.length; ++i) {
if( seedAddrsResolved[i] == null ) {
seedAddrsResolved[i] = lookup( proxy, seedAddrs[i] );
}
}
return seedAddrsResolved;
}
/**
* Resolves a hostname via remote DNS over socks5 proxy.
*/
@Nullable
public static InetSocketAddress lookup( Socks5Proxy proxy, InetSocketAddress addr ) {
if( !addr.isUnresolved() ) {
return addr;
}
try {
SocksSocket proxySocket = new SocksSocket( proxy, addr.getHostString(), addr.getPort() );
InetAddress addrResolved = proxySocket.getInetAddress();
proxySocket.close();
if( addrResolved != null ) {
log.info("Resolved " + addr.getHostString() + " to " + addrResolved.getHostAddress() );
return new InetSocketAddress(addrResolved, addr.getPort() );
}
else {
// note: .onion nodes fall in here when proxy is Tor. But they have no IP address.
// Unfortunately bitcoinj crashes in PeerAddress if it finds an unresolved address.
log.error("Connected to " + addr.getHostString() + ". But did not resolve to address." );
}
} catch (Exception e) {
log.warn("Error resolving " + addr.getHostString() + ". Exception:\n" + e.toString());
}
return null;
}
/**
* Converts an array of hostnames to array of unresolved InetSocketAddress
*/
private InetSocketAddress[] convertAddrsString(String[] addrs, int port) {
InetSocketAddress[] list = new InetSocketAddress[addrs.length];
for( int i = 0; i < addrs.length; i++) {
list[i] = InetSocketAddress.createUnresolved(addrs[i], port);
}
return list;
}
@Override
public void shutdown() {
}
}

View file

@ -0,0 +1,68 @@
/*
* 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.btc;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.PeerGroup;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.net.BlockingClientManager;
import java.io.File;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeoutException;
public class WalletAppKitBitSquare extends WalletAppKit {
private Socks5Proxy socks5Proxy;
/**
* Creates a new WalletAppKit, with a newly created {@link Context}. Files will be stored in the given directory.
*/
public WalletAppKitBitSquare(NetworkParameters params, Socks5Proxy socks5Proxy, File directory, String filePrefix) {
super(params, directory, filePrefix);
this.socks5Proxy = socks5Proxy;
}
public Socks5Proxy getProxy() {
return socks5Proxy;
}
protected PeerGroup createPeerGroup() throws TimeoutException {
// no proxy case.
if (socks5Proxy == null) {
return super.createPeerGroup();
}
// proxy case.
Proxy proxy = new Proxy(Proxy.Type.SOCKS,
new InetSocketAddress(socks5Proxy.getInetAddress().getHostName(),
socks5Proxy.getPort()));
int CONNECT_TIMEOUT_MSEC = 60 * 1000; // same value used in bitcoinj.
ProxySocketFactory proxySocketFactory = new ProxySocketFactory(proxy);
BlockingClientManager mgr = new BlockingClientManager(proxySocketFactory);
PeerGroup peerGroup = new PeerGroup(params, vChain, mgr);
mgr.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC);
peerGroup.setConnectTimeoutMillis(CONNECT_TIMEOUT_MSEC);
return peerGroup;
}
}

View file

@ -22,6 +22,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.Service;
import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
import io.bitsquare.app.Log;
import io.bitsquare.btc.listeners.AddressConfidenceListener;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.listeners.TxConfidenceListener;
@ -30,6 +32,7 @@ import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ExceptionHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.network.Socks5ProxyProvider;
import io.bitsquare.storage.FileUtil;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.Preferences;
@ -37,7 +40,7 @@ import javafx.beans.property.*;
import org.bitcoinj.core.*;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.net.discovery.SeedPeers;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
@ -54,6 +57,7 @@ import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Paths;
import java.util.*;
@ -71,8 +75,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class WalletService {
private static final Logger log = LoggerFactory.getLogger(WalletService.class);
public static final String DIR_KEY = "walletDir";
public static final String PREFIX_KEY = "walletPrefix";
private static final long STARTUP_TIMEOUT_SEC = 60;
private final CopyOnWriteArraySet<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArraySet<>();
@ -85,12 +87,15 @@ public class WalletService {
private final RegTestHost regTestHost;
private final TradeWalletService tradeWalletService;
private final AddressEntryList addressEntryList;
private final Preferences preferences;
private final Socks5ProxyProvider socks5ProxyProvider;
private final String seedNodes;
private final NetworkParameters params;
private final File walletDir;
private final UserAgent userAgent;
private final boolean useTor;
private WalletAppKit walletAppKit;
private WalletAppKitBitSquare walletAppKit;
private Wallet wallet;
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final ObjectProperty<List<Peer>> connectedPeers = new SimpleObjectProperty<>();
@ -105,18 +110,37 @@ public class WalletService {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public WalletService(RegTestHost regTestHost, TradeWalletService tradeWalletService, AddressEntryList addressEntryList, UserAgent userAgent,
@Named(DIR_KEY) File appDir, Preferences preferences) {
public WalletService(RegTestHost regTestHost,
TradeWalletService tradeWalletService,
AddressEntryList addressEntryList,
UserAgent userAgent,
Preferences preferences,
Socks5ProxyProvider socks5ProxyProvider,
@Named(BtcOptionKeys.WALLET_DIR) File appDir,
@Named(BtcOptionKeys.BTC_SEED_NODES) String seedNodes,
@Named(BtcOptionKeys.USE_TOR_FOR_BTC) String useTorFlagFromOptions) {
this.regTestHost = regTestHost;
this.tradeWalletService = tradeWalletService;
this.addressEntryList = addressEntryList;
this.preferences = preferences;
this.socks5ProxyProvider = socks5ProxyProvider;
this.seedNodes = seedNodes;
this.params = preferences.getBitcoinNetwork().getParameters();
this.walletDir = new File(appDir, "bitcoin");
this.userAgent = userAgent;
// We support a checkbox in the settings to set the use tor flag.
// If we get the options set we override that setting.
if (useTorFlagFromOptions != null && !useTorFlagFromOptions.isEmpty()) {
if (useTorFlagFromOptions.equals("false"))
preferences.setUseTorForBitcoinJ(false);
else if (useTorFlagFromOptions.equals("true"))
preferences.setUseTorForBitcoinJ(true);
}
useTor = preferences.getUseTorForBitcoinJ();
storage = new Storage<>(walletDir);
Long persisted = storage.initAndGetPersisted("BloomFilterNonce");
Long persisted = storage.initAndGetPersistedWithFileName("BloomFilterNonce");
if (persisted != null) {
bloomFilterTweak = persisted;
} else {
@ -131,6 +155,7 @@ public class WalletService {
///////////////////////////////////////////////////////////////////////////////////////////
public void initialize(@Nullable DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
Log.traceCall();
// Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means
// we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener
// we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in
@ -142,11 +167,13 @@ public class WalletService {
exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " +
STARTUP_TIMEOUT_SEC + " seconds.")), STARTUP_TIMEOUT_SEC);
backupWallet();
final Socks5Proxy socks5Proxy = preferences.getUseTorForBitcoinJ() ? socks5ProxyProvider.getSocks5Proxy() : null;
log.info("Use socks5Proxy for bitcoinj: " + socks5Proxy);
// If seed is non-null it means we are restoring from backup.
walletAppKit = new WalletAppKit(params, walletDir, "Bitsquare") {
walletAppKit = new WalletAppKitBitSquare(params, socks5Proxy, walletDir, "Bitsquare") {
@Override
protected void onSetupCompleted() {
// Don't make the user wait for confirmations for now, as the intention is they're sending it
@ -247,11 +274,48 @@ public class WalletService {
// 1333 / (2800 + 1333) = 0.32 -> 32 % probability that a pub key is in our wallet
walletAppKit.setBloomFilterFalsePositiveRate(0.00005);
log.debug("seedNodes: " + seedNodes);
boolean usePeerNodes = false;
// TODO Get bitcoinj running over our tor proxy. BlockingClientManager need to be used to use the socket
// from jtorproxy. To get supported it via nio / netty will be harder
if (useTor && params.getId().equals(NetworkParameters.ID_MAINNET))
walletAppKit.useTor();
// Pass custom seed nodes if set in options
if (!seedNodes.isEmpty()) {
// TODO: this parsing should be more robust,
// give validation error if needed.
String[] nodes = seedNodes.replace(", ", ",").split(",");
List<PeerAddress> peerAddressList = new ArrayList<>();
for (String node : nodes) {
String[] parts = node.split(":");
if (parts.length == 1) {
// port not specified. Use default port for network.
parts = new String[]{parts[0], Integer.toString(params.getPort())};
}
if (parts.length == 2) {
// note: this will cause a DNS request if hostname used.
// note: DNS requests are routed over socks5 proxy, if used.
// fixme: .onion hostnames will fail! see comments in SeedPeersSocks5Dns
InetSocketAddress addr;
if (socks5Proxy != null) {
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1]));
// proxy remote DNS request happens here.
addr = SeedPeersSocks5Dns.lookup(socks5Proxy, unresolved);
} else {
// DNS request happens here. if it fails, addr.isUnresolved() == true.
addr = new InetSocketAddress(parts[0], Integer.parseInt(parts[1]));
}
// note: isUnresolved check should be removed once we fix PeerAddress
if (addr != null && !addr.isUnresolved())
peerAddressList.add(new PeerAddress(addr.getAddress(), addr.getPort()));
}
}
if (peerAddressList.size() > 0) {
PeerAddress peerAddressListFixed[] = new PeerAddress[peerAddressList.size()];
log.debug("seedNodes parsed: " + Arrays.toString(peerAddressListFixed));
walletAppKit.setPeerNodes(peerAddressList.toArray(peerAddressListFixed));
usePeerNodes = true;
}
}
// Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
// or progress widget to keep the user engaged whilst we initialise, but we don't.
@ -259,6 +323,7 @@ public class WalletService {
if (regTestHost == RegTestHost.REG_TEST_SERVER) {
try {
walletAppKit.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
usePeerNodes = true;
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
@ -280,6 +345,23 @@ public class WalletService {
walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints.testnet"));
}
// If operating over a proxy and we haven't set any peer nodes, then
// we want to use SeedPeers for discovery instead of the default DnsDiscovery.
// This is only because we do not yet have a Dns discovery class that works
// reliably over proxy/tor.
//
// todo: There should be a user pref called "Use Local DNS for Proxy/Tor"
// that disables this. In that case, the default DnsDiscovery class will
// be used which should work, but is less private. The aim here is to
// be private by default when using proxy/tor. However, the seedpeers
// could become outdated, so it is important that the user be able to
// disable it, but should be made aware of the reduced privacy.
if (socks5Proxy != null && !usePeerNodes) {
// SeedPeersSocks5Dns should replace SeedPeers once working reliably.
// SeedPeers uses hard coded stable addresses (from MainNetParams). It should be updated from time to time.
walletAppKit.setDiscovery(new SeedPeers(params));
}
walletAppKit.setDownloadListener(downloadListener)
.setBlockingStartup(false)
.setUserAgent(userAgent.getName(), userAgent.getVersion())
@ -695,7 +777,8 @@ public class WalletService {
// in some cases getFee did not calculate correctly and we still get an InsufficientMoneyException
log.warn("We still have a missing fee " + (e.missing != null ? e.missing.toFriendlyString() : ""));
amount = (amount.subtract(e.missing));
if (e != null)
amount = amount.subtract(e.missing);
newTransaction.clearOutputs();
newTransaction.addOutput(amount, toAddress);
@ -933,7 +1016,8 @@ public class WalletService {
if (!addressEntry.isPresent())
throw new AddressEntryException("WithdrawFromAddress is not found in our wallet.");
checkNotNull(addressEntry.get().getAddress(), "addressEntry.get().getAddress() must nto be null");
checkNotNull(addressEntry.get(), "addressEntry.get() must not be null");
checkNotNull(addressEntry.get().getAddress(), "addressEntry.get().getAddress() must not be null");
sendRequest.coinSelector = new TradeWalletCoinSelector(params, addressEntry.get().getAddress());
sendRequest.changeAddress = addressEntry.get().getAddress();
sendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();

View file

@ -20,10 +20,11 @@ import java.util.Arrays;
public class BlockchainService {
private static final Logger log = LoggerFactory.getLogger(BlockchainService.class);
private final ArrayList<FeeProvider> feeProviders = new ArrayList<>(Arrays.asList(new BlockrIOProvider(), new BlockTrailProvider(), new TradeBlockProvider()));
private final ArrayList<FeeProvider> feeProviders;
@Inject
public BlockchainService() {
public BlockchainService(BlockrIOProvider blockrIOProvider, BlockTrailProvider blockTrailProvider, TradeBlockProvider tradeBlockProvider) {
feeProviders = new ArrayList<>(Arrays.asList(blockrIOProvider, blockTrailProvider, tradeBlockProvider));
}
public SettableFuture<Coin> requestFee(String transactionId) {

View file

@ -5,19 +5,20 @@ import com.google.gson.JsonParser;
import io.bitsquare.app.Log;
import io.bitsquare.http.HttpClient;
import io.bitsquare.http.HttpException;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
public class BlockTrailProvider implements FeeProvider {
public class BlockTrailProvider extends FeeProvider {
private static final Logger log = LoggerFactory.getLogger(BlockTrailProvider.class);
private final HttpClient httpClient;
public BlockTrailProvider() {
httpClient = new HttpClient("https://www.blocktrail.com/BTC/json/blockchain/tx/");
@Inject
public BlockTrailProvider(HttpClient httpClient, Preferences preferences) {
super(httpClient, preferences, "https://www.blocktrail.com/BTC/json/blockchain/tx/");
}
@Override

View file

@ -5,22 +5,22 @@ import com.google.gson.JsonParser;
import io.bitsquare.app.Log;
import io.bitsquare.http.HttpClient;
import io.bitsquare.http.HttpException;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
public class BlockrIOProvider implements FeeProvider {
public class BlockrIOProvider extends FeeProvider {
private static final Logger log = LoggerFactory.getLogger(BlockrIOProvider.class);
private final HttpClient httpClient;
public BlockrIOProvider() {
httpClient = new HttpClient("https://btc.blockr.io/api/v1/tx/info/");
@Inject
public BlockrIOProvider(HttpClient httpClient, Preferences preferences) {
super(httpClient, preferences, "https://btc.blockr.io/api/v1/tx/info/");
}
//https://api.bitcoinaverage.com/ticker/global/EUR/
@Override
public Coin getFee(String transactionId) throws IOException, HttpException {
Log.traceCall("transactionId=" + transactionId);

View file

@ -1,10 +1,17 @@
package io.bitsquare.btc.blockchain.providers;
import io.bitsquare.btc.HttpClientProvider;
import io.bitsquare.http.HttpClient;
import io.bitsquare.http.HttpException;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.Coin;
import java.io.IOException;
public interface FeeProvider {
Coin getFee(String transactionId) throws IOException, HttpException;
public abstract class FeeProvider extends HttpClientProvider {
public FeeProvider(HttpClient httpClient, Preferences preferences, String baseUrl) {
super(httpClient, preferences, baseUrl);
}
public abstract Coin getFee(String transactionId) throws IOException, HttpException;
}

View file

@ -5,19 +5,20 @@ import com.google.gson.JsonParser;
import io.bitsquare.app.Log;
import io.bitsquare.http.HttpClient;
import io.bitsquare.http.HttpException;
import io.bitsquare.user.Preferences;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
public class TradeBlockProvider implements FeeProvider {
public class TradeBlockProvider extends FeeProvider {
private static final Logger log = LoggerFactory.getLogger(TradeBlockProvider.class);
private final HttpClient httpClient;
public TradeBlockProvider() {
httpClient = new HttpClient("https://tradeblock.com/api/blockchain/tx/");
@Inject
public TradeBlockProvider(HttpClient httpClient, Preferences preferences) {
super(httpClient, preferences, "https://tradeblock.com/api/blockchain/tx/");
}
@Override

View file

@ -30,7 +30,7 @@ public class MarketPrice {
}
public double getPrice(PriceFeed.Type type) {
public double getPrice(PriceFeedService.Type type) {
switch (type) {
case ASK:
return ask;

View file

@ -24,8 +24,8 @@ import java.util.Random;
import java.util.function.Consumer;
// TODO use https://github.com/timmolter/XChange
public class PriceFeed {
private static final Logger log = LoggerFactory.getLogger(PriceFeed.class);
public class PriceFeedService {
private static final Logger log = LoggerFactory.getLogger(PriceFeedService.class);
private static final long MIN_PERIOD_BETWEEN_CALLS = 5000;
@ -51,8 +51,8 @@ public class PriceFeed {
private static final long PERIOD_ALL_CRYPTO_SEC = new Random().nextInt(5) + 180;
private final Map<String, MarketPrice> cache = new HashMap<>();
private final PriceProvider fiatPriceProvider = new BitcoinAveragePriceProvider();
private final PriceProvider cryptoCurrenciesPriceProvider = new PoloniexPriceProvider();
private final PriceProvider fiatPriceProvider;
private final PriceProvider cryptoCurrenciesPriceProvider;
private Consumer<Double> priceConsumer;
private FaultHandler faultHandler;
private Type type;
@ -72,7 +72,9 @@ public class PriceFeed {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public PriceFeed() {
public PriceFeedService(BitcoinAveragePriceProvider fiatPriceProvider, PoloniexPriceProvider cryptoCurrenciesPriceProvider) {
this.fiatPriceProvider = fiatPriceProvider;
this.cryptoCurrenciesPriceProvider = cryptoCurrenciesPriceProvider;
}

View file

@ -7,25 +7,27 @@ import com.google.gson.internal.LinkedTreeMap;
import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.http.HttpClient;
import io.bitsquare.http.HttpException;
import io.bitsquare.user.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class BitcoinAveragePriceProvider implements PriceProvider {
public class BitcoinAveragePriceProvider extends PriceProvider {
private static final Logger log = LoggerFactory.getLogger(BitcoinAveragePriceProvider.class);
private final HttpClient httpClient = new HttpClient("https://api.bitcoinaverage.com/ticker/global/");
public BitcoinAveragePriceProvider() {
@Inject
public BitcoinAveragePriceProvider(HttpClient httpClient, Preferences preferences) {
super(httpClient, preferences, "https://api.bitcoinaverage.com/ticker/global/", false);
}
@Override
public Map<String, MarketPrice> getAllPrices() throws IOException, HttpException {
Map<String, MarketPrice> marketPriceMap = new HashMap<>();
LinkedTreeMap<String, Object> treeMap = new Gson().fromJson(httpClient.requestWithGET("all"), LinkedTreeMap.class);
LinkedTreeMap<String, Object> treeMap = new Gson().<LinkedTreeMap<String, Object>>fromJson(httpClient.requestWithGET("all"), LinkedTreeMap.class);
Map<String, String> temp = new HashMap<>();
treeMap.entrySet().stream().forEach(e -> {
Object value = e.getValue();
@ -56,7 +58,6 @@ public class BitcoinAveragePriceProvider implements PriceProvider {
@Override
public String toString() {
return "BitcoinAveragePriceProvider{" +
'}';
return "BitcoinAveragePriceProvider";
}
}

View file

@ -9,22 +9,27 @@ import io.bitsquare.http.HttpClient;
import io.bitsquare.http.HttpException;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.network.Socks5ProxyProvider;
import io.bitsquare.user.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class PoloniexPriceProvider implements PriceProvider {
public class PoloniexPriceProvider extends PriceProvider {
private static final Logger log = LoggerFactory.getLogger(PoloniexPriceProvider.class);
//https://poloniex.com/public?command=returnTicker
private final HttpClient httpClient = new HttpClient("https://poloniex.com/public");
public PoloniexPriceProvider() {
@Inject
public PoloniexPriceProvider(HttpClient httpClient, Preferences preferences, Socks5ProxyProvider socks5ProxyProvider) {
// Poloniex uses Cloudflare which requires a captcha if they get connected from a Tor exit node.
// We can't use Tor for Poloniex for that reason and set the ignoreSocks5Proxy flag to true if no
// custom socks5ProxyHttp is set.
super(httpClient, preferences, "https://poloniex.com/public", socks5ProxyProvider.getSocks5ProxyHttp() == null);
}
@Override
@ -74,7 +79,6 @@ public class PoloniexPriceProvider implements PriceProvider {
@Override
public String toString() {
return "PoloniexPriceProvider{" +
'}';
return "PoloniexPriceProvider";
}
}

View file

@ -1,14 +1,21 @@
package io.bitsquare.btc.pricefeed.providers;
import io.bitsquare.btc.HttpClientProvider;
import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.http.HttpClient;
import io.bitsquare.http.HttpException;
import io.bitsquare.user.Preferences;
import java.io.IOException;
import java.io.Serializable;
import java.util.Map;
public interface PriceProvider extends Serializable {
Map<String, MarketPrice> getAllPrices() throws IOException, HttpException;
public abstract class PriceProvider extends HttpClientProvider {
MarketPrice getPrice(String currencyCode) throws IOException, HttpException;
public PriceProvider(HttpClient httpClient, Preferences preferences, String baseUrl, boolean ignoreSocks5Proxy) {
super(httpClient, preferences, baseUrl, ignoreSocks5Proxy);
}
abstract public Map<String, MarketPrice> getAllPrices() throws IOException, HttpException;
abstract public MarketPrice getPrice(String currencyCode) throws IOException, HttpException;
}

View file

@ -19,7 +19,7 @@ package io.bitsquare.filter;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.bitsquare.common.OptionKeys;
import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.common.util.Utilities;
@ -59,7 +59,7 @@ public class FilterManager {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public FilterManager(P2PService p2PService, KeyRing keyRing, User user, @Named(OptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
public FilterManager(P2PService p2PService, KeyRing keyRing, User user, @Named(CoreOptionKeys.IGNORE_DEV_MSG_KEY) boolean ignoreDevMsg) {
this.p2PService = p2PService;
this.keyRing = keyRing;
this.user = user;

View file

@ -19,7 +19,7 @@ package io.bitsquare.filter;
import com.google.inject.Singleton;
import io.bitsquare.app.AppModule;
import io.bitsquare.common.OptionKeys;
import io.bitsquare.app.CoreOptionKeys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
@ -36,6 +36,6 @@ public class FilterModule extends AppModule {
@Override
protected final void configure() {
bind(FilterManager.class).in(Singleton.class);
bindConstant().annotatedWith(named(OptionKeys.IGNORE_DEV_MSG_KEY)).to(env.getRequiredProperty(OptionKeys.IGNORE_DEV_MSG_KEY));
bindConstant().annotatedWith(named(CoreOptionKeys.IGNORE_DEV_MSG_KEY)).to(env.getRequiredProperty(CoreOptionKeys.IGNORE_DEV_MSG_KEY));
}
}

View file

@ -83,7 +83,7 @@ public class BankUtil {
case "HK":
return "Bank code:";
default:
return "Bank nr. (e.g. BIC or SWIFT) (optional):";
return "Bank ID (e.g. BIC or SWIFT) (optional):";
}
}

View file

@ -20,11 +20,14 @@ package io.bitsquare.locale;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import io.bitsquare.user.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
public class CountryUtil {
private static final Logger log = LoggerFactory.getLogger(CountryUtil.class);
public static List<Country> getAllSepaEuroCountries() {
List<Country> list = new ArrayList<>();
@ -147,7 +150,8 @@ public class CountryUtil {
allLocales = new ArrayList<>();
allLocales.addAll(allLocalesAsSet);
allLocales.add(new Locale("", "MD", ""));
allLocales.add(new Locale("", "MD", "")); // Moldava
allLocales.add(new Locale("", "KH", "")); // Cambodia
allLocales.sort((locale1, locale2) -> locale1.getDisplayCountry().compareTo(locale2.getDisplayCountry()));
return allLocales;
}
@ -160,7 +164,7 @@ public class CountryUtil {
private static final String[] countryCodes = new String[]{"AE", "AL", "AR", "AT", "AU", "BA", "BE", "BG", "BH",
"BO", "BR", "BY", "CA", "CH", "CL", "CN", "CO", "CR", "CS", "CU", "CY", "CZ", "DE", "DK", "DO", "DZ",
"EC", "EE", "EG", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HR", "HU", "ID", "IE", "IL", "IN",
"IQ", "IS", "IT", "JO", "JP", "KR", "KW", "LB", "LT", "LU", "LV", "LY", "MA", "MD", "ME", "MK", "MT", "MX",
"IQ", "IS", "IT", "JO", "JP", "KH", "KR", "KW", "LB", "LT", "LU", "LV", "LY", "MA", "MD", "ME", "MK", "MT", "MX",
"MY", "NI", "NL", "NO", "NZ", "OM", "PA", "PE", "PH", "PL", "PR", "PT", "PY", "QA", "RO", "RS", "RU",
"SA", "SD", "SE", "SG", "SI", "SK", "SV", "SY", "TH", "TN", "TR", "TW", "UA", "US", "UY", "VE", "VN",
"YE", "ZA"};
@ -169,7 +173,7 @@ public class CountryUtil {
private static final String[] regionCodes = new String[]{"AS", "EU", "SA", "EU", "OC", "EU", "EU", "EU", "AS",
"SA", "SA", "EU", "NA", "EU", "SA", "AS", "SA", "NA", "EU", "NA", "EU", "EU", "EU", "EU", "NA", "AF",
"SA", "EU", "AF", "EU", "EU", "EU", "EU", "EU", "NA", "AS", "NA", "EU", "EU", "AS", "EU", "AS", "AS",
"AS", "EU", "EU", "AS", "AS", "AS", "AS", "AS", "EU", "EU", "EU", "AF", "AF", "EU", "EU", "EU", "EU", "NA",
"AS", "EU", "EU", "AS", "AS", "AS", "AS", "AS", "AS", "EU", "EU", "EU", "AF", "AF", "EU", "EU", "EU", "EU", "NA",
"AS", "NA", "EU", "EU", "OC", "AS", "NA", "SA", "AS", "EU", "NA", "EU", "SA", "AS", "EU", "EU", "EU",
"AS", "AF", "EU", "AS", "EU", "EU", "NA", "AS", "AS", "AF", "AS", "AS", "EU", "NA", "SA", "SA", "AS",
"AS", "AF"};

View file

@ -75,7 +75,10 @@ public class CurrencyUtil {
result.add(new CryptoCurrency("XMR", "Monero"));
result.add(new CryptoCurrency("SC", "Siacoin"));
result.add(new CryptoCurrency("ETH", "Ether"));
result.add(new CryptoCurrency("ETHC", "EtherClassic"));
result.add(new CryptoCurrency("ETC", "EtherClassic"));
result.add(new CryptoCurrency("STEEM", "STEEM"));
result.add(new CryptoCurrency("FLO", "FlorinCoin"));
result.add(new CryptoCurrency("XEM", "NEM"));
result.add(new CryptoCurrency("LTC", "Litecoin"));
result.add(new CryptoCurrency("DASH", "Dash"));
result.add(new CryptoCurrency("NMC", "Namecoin"));
@ -90,16 +93,12 @@ public class CurrencyUtil {
result.add(new CryptoCurrency("BLK", "Blackcoin"));
result.add(new CryptoCurrency("FCT", "Factom"));
result.add(new CryptoCurrency("NXT", "Nxt"));
result.add(new CryptoCurrency("STEEM", "STEEM"));
result.add(new CryptoCurrency("BTS", "BitShares"));
result.add(new CryptoCurrency("XCP", "Counterparty"));
result.add(new CryptoCurrency("XRP", "Ripple"));
result.add(new CryptoCurrency("FAIR", "FairCoin"));
result.add(new CryptoCurrency("FLO", "FlorinCoin"));
result.add(new CryptoCurrency("MKR", "Maker", true));
result.add(new CryptoCurrency("DGD", "DigixDAO Tokens", true));
result.add(new CryptoCurrency("DAO", "DAO", true));
result.add(new CryptoCurrency("XEM", "NEM"));
result.add(new CryptoCurrency("ANTI", "Anti"));
result.add(new CryptoCurrency("VPN", "VPNCoin"));
result.add(new CryptoCurrency("MAID", "MaidSafeCoin"));
@ -131,23 +130,25 @@ public class CurrencyUtil {
result.add(new CryptoCurrency("EURT", "EUR Tether"));
result.add(new CryptoCurrency("JPYT", "JPY Tether"));
result.add(new CryptoCurrency("WDC", "Worldcoin"));
result.add(new CryptoCurrency("DAO", "DAO", true));
result.add(new CryptoCurrency("ETHC", "EtherClassic (deprecated ticker symbol)"));
return result;
}
}
public static List<CryptoCurrency> getMainCryptoCurrencies() {
final List<CryptoCurrency> result = new ArrayList<>();
result.add(new CryptoCurrency("XMR", "Monero"));
result.add(new CryptoCurrency("SC", "Siacoin"));
result.add(new CryptoCurrency("ETH", "Ether"));
result.add(new CryptoCurrency("ETHC", "EtherClassic"));
result.add(new CryptoCurrency("ETC", "EtherClassic"));
result.add(new CryptoCurrency("STEEM", "STEEM"));
result.add(new CryptoCurrency("FLO", "FlorinCoin"));
result.add(new CryptoCurrency("LTC", "Litecoin"));
result.add(new CryptoCurrency("DASH", "Dash"));
result.add(new CryptoCurrency("NMC", "Namecoin"));
result.add(new CryptoCurrency("NBT", "NuBits"));
result.add(new CryptoCurrency("SDC", "ShadowCash"));
result.add(new CryptoCurrency("DOGE", "Dogecoin"));
result.add(new CryptoCurrency("NXT", "Nxt"));
result.add(new CryptoCurrency("STEEM", "STEEM"));
result.add(new CryptoCurrency("BTS", "BitShares"));
return result;
}

View file

@ -97,7 +97,7 @@ public abstract class BankAccountContractData extends CountryBasedPaymentAccount
@Nullable
public String getBankId() {
return bankId;
return BankUtil.isBankIdRequired(countryCode) ? bankId : bankName;
}
public void setBranchId(String branchId) {

View file

@ -0,0 +1,91 @@
package io.bitsquare.payment;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.trade.offer.Offer;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
public class PaymentAccountUtil {
private static final Logger log = LoggerFactory.getLogger(PaymentAccountUtil.class);
public static boolean isAnyPaymentAccountValidForOffer(Offer offer, Collection<PaymentAccount> paymentAccounts) {
for (PaymentAccount paymentAccount : paymentAccounts) {
if (isPaymentAccountValidForOffer(offer, paymentAccount))
return true;
}
return false;
}
public static ObservableList<PaymentAccount> getPossiblePaymentAccounts(Offer offer, Set<PaymentAccount> paymentAccounts) {
ObservableList<PaymentAccount> result = FXCollections.observableArrayList();
for (PaymentAccount paymentAccount : paymentAccounts) {
if (isPaymentAccountValidForOffer(offer, paymentAccount))
result.add(paymentAccount);
}
return result;
}
//TODO not tested with all combinations yet....
public static boolean isPaymentAccountValidForOffer(Offer offer, PaymentAccount paymentAccount) {
// check if we have a matching currency
Set<String> paymentAccountCurrencyCodes = paymentAccount.getTradeCurrencies().stream().map(TradeCurrency::getCode).collect(Collectors.toSet());
boolean matchesCurrencyCode = paymentAccountCurrencyCodes.contains(offer.getCurrencyCode());
if (!matchesCurrencyCode)
return false;
// check if we have a matching payment method or if its a bank account payment method which is treated special
if (paymentAccount instanceof CountryBasedPaymentAccount) {
CountryBasedPaymentAccount countryBasedPaymentAccount = (CountryBasedPaymentAccount) paymentAccount;
// check if we have a matching country
boolean matchesCountryCodes = offer.getAcceptedCountryCodes() != null && countryBasedPaymentAccount.getCountry() != null &&
offer.getAcceptedCountryCodes().contains(countryBasedPaymentAccount.getCountry().code);
if (!matchesCountryCodes)
return false;
if (paymentAccount instanceof SepaAccount || offer.getPaymentMethod().equals(PaymentMethod.SEPA)) {
boolean samePaymentMethod = paymentAccount.getPaymentMethod().equals(offer.getPaymentMethod());
return samePaymentMethod;
} else if (offer.getPaymentMethod().equals(PaymentMethod.SAME_BANK) ||
offer.getPaymentMethod().equals(PaymentMethod.SPECIFIC_BANKS)) {
checkNotNull(offer.getAcceptedBankIds(), "offer.getAcceptedBankIds() must not be null");
if (paymentAccount instanceof SpecificBanksAccount) {
// check if we have a matching bank
boolean offerSideMatchesBank = offer.getAcceptedBankIds().contains(((BankAccount) paymentAccount).getBankId());
boolean paymentAccountSideMatchesBank = ((SpecificBanksAccount) paymentAccount).getAcceptedBanks().contains(offer.getBankId());
return offerSideMatchesBank && paymentAccountSideMatchesBank;
} else {
// national or same bank
boolean matchesBank = offer.getAcceptedBankIds().contains(((BankAccount) paymentAccount).getBankId());
return matchesBank;
}
} else {
if (paymentAccount instanceof SpecificBanksAccount) {
// check if we have a matching bank
boolean paymentAccountSideMatchesBank = ((SpecificBanksAccount) paymentAccount).getAcceptedBanks().contains(offer.getBankId());
return paymentAccountSideMatchesBank;
} else if (paymentAccount instanceof SameBankAccount) {
// check if we have a matching bank
boolean paymentAccountSideMatchesBank = ((SameBankAccount) paymentAccount).getBankId().equals(offer.getBankId());
return paymentAccountSideMatchesBank;
} else {
// national
return true;
}
}
} else {
return paymentAccount.getPaymentMethod().equals(offer.getPaymentMethod());
}
}
}

View file

@ -24,7 +24,8 @@ import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.AddressEntryException;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.FaultHandler;
@ -47,6 +48,8 @@ import io.bitsquare.trade.offer.OpenOfferManager;
import io.bitsquare.trade.protocol.availability.OfferAvailabilityModel;
import io.bitsquare.trade.protocol.trade.messages.PayDepositRequest;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import io.bitsquare.trade.statistics.TradeStatistics;
import io.bitsquare.trade.statistics.TradeStatisticsManager;
import io.bitsquare.user.User;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@ -63,9 +66,8 @@ import org.spongycastle.crypto.params.KeyParameter;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import static io.bitsquare.util.Validator.nonEmptyStringOf;
@ -73,6 +75,8 @@ import static io.bitsquare.util.Validator.nonEmptyStringOf;
public class TradeManager {
private static final Logger log = LoggerFactory.getLogger(TradeManager.class);
private static final long REPUBLISH_STATISTICS_INTERVAL_MIN = TimeUnit.HOURS.toMillis(1);
private final User user;
private final KeyRing keyRing;
private final WalletService walletService;
@ -82,11 +86,14 @@ public class TradeManager {
private final FailedTradesManager failedTradesManager;
private final ArbitratorManager arbitratorManager;
private final P2PService p2PService;
private FilterManager filterManager;
private final FilterManager filterManager;
private final TradeStatisticsManager tradeStatisticsManager;
private final Storage<TradableList<Trade>> tradableListStorage;
private final TradableList<Trade> trades;
private final BooleanProperty pendingTradesInitialized = new SimpleBooleanProperty();
private boolean stopped;
private List<Trade> tradesForStatistics;
///////////////////////////////////////////////////////////////////////////////////////////
@ -103,8 +110,9 @@ public class TradeManager {
FailedTradesManager failedTradesManager,
ArbitratorManager arbitratorManager,
P2PService p2PService,
PriceFeed priceFeed,
PriceFeedService priceFeedService,
FilterManager filterManager,
TradeStatisticsManager tradeStatisticsManager,
@Named(Storage.DIR_KEY) File storageDir) {
this.user = user;
this.keyRing = keyRing;
@ -116,10 +124,11 @@ public class TradeManager {
this.arbitratorManager = arbitratorManager;
this.p2PService = p2PService;
this.filterManager = filterManager;
this.tradeStatisticsManager = tradeStatisticsManager;
tradableListStorage = new Storage<>(storageDir);
trades = new TradableList<>(tradableListStorage, "PendingTrades");
trades.forEach(e -> e.getOffer().setPriceFeed(priceFeed));
trades.forEach(e -> e.getOffer().setPriceFeedService(priceFeedService));
p2PService.addDecryptedDirectMessageListener(new DecryptedDirectMessageListener() {
@Override
@ -158,6 +167,11 @@ public class TradeManager {
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
public void onAllServicesInitialized() {
Log.traceCall();
if (p2PService.isBootstrapped())
@ -173,37 +187,80 @@ public class TradeManager {
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
public void shutDown() {
stopped = true;
}
private void initPendingTrades() {
Log.traceCall();
List<Trade> toAdd = new ArrayList<>();
List<Trade> toRemove = new ArrayList<>();
tradesForStatistics = new ArrayList<>();
for (Trade trade : trades) {
trade.setStorage(tradableListStorage);
if (trade.isDepositPaid() || (trade.isTakerFeePaid() && trade.errorMessageProperty().get() == null)) {
initTrade(trade, trade.getProcessModel().getUseSavingsWallet(), trade.getProcessModel().getFundsNeededForTrade());
trade.updateDepositTxFromWallet();
tradesForStatistics.add(trade);
} else if (trade.isTakerFeePaid()) {
toAdd.add(trade);
} else {
toRemove.add(trade);
}
}
for (Trade trade : toAdd) {
for (Trade trade : toAdd)
addTradeToFailedTrades(trade);
}
for (Trade trade : toRemove) {
for (Trade trade : toRemove)
removePreparedTrade(trade);
for (Tradable tradable : closedTradableManager.getClosedTrades()) {
if (tradable instanceof Trade)
tradesForStatistics.add((Trade) tradable);
}
// We start later to have better connectivity to the network
UserThread.runPeriodically(() -> publishTradeStatistics(tradesForStatistics),
30, TimeUnit.SECONDS);
//TODO can be removed at next release
// For the first 2 weeks of the release we re publish the trades to get faster good distribution
// otherwise the trades would only be published again at restart and if a client dont do that the stats might be missing
// for a longer time as initially there are not many peer upgraded and supporting flooding of the stats data.
if (new Date().before(new Date(2016 - 1900, Calendar.AUGUST, 8)))
UserThread.runPeriodically(() -> publishTradeStatistics(tradesForStatistics),
REPUBLISH_STATISTICS_INTERVAL_MIN, TimeUnit.MILLISECONDS);
pendingTradesInitialized.set(true);
}
private void publishTradeStatistics(List<Trade> trades) {
for (int i = 0; i < trades.size(); i++) {
Trade trade = trades.get(i);
TradeStatistics tradeStatistics = new TradeStatistics(trade.getOffer(),
trade.getTradePrice(),
trade.getTradeAmount(),
trade.getDate(),
(trade.getDepositTx() != null ? trade.getDepositTx().getHashAsString() : ""),
keyRing.getPubKeyRing());
tradeStatisticsManager.add(tradeStatistics);
// Only trades from last 30 days
if ((new Date().getTime() - trade.getDate().getTime()) < TimeUnit.DAYS.toMillis(30)) {
long delay = 3000;
final long minDelay = (i + 1) * delay;
final long maxDelay = (i + 2) * delay;
UserThread.runAfterRandomDelay(() -> {
if (!stopped)
p2PService.addData(tradeStatistics, true);
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
}
}
}
private void handleInitialTakeOfferRequest(TradeMessage message, NodeAddress peerNodeAddress) {
log.trace("handleNewMessage: message = " + message.getClass().getSimpleName() + " from " + peerNodeAddress);
try {
@ -289,7 +346,7 @@ public class TradeManager {
if (offer.getState() == Offer.State.AVAILABLE)
createTrade(amount, tradePrice, fundsNeededForTrade, offer, paymentAccountId, useSavingsWallet, model, tradeResultHandler);
},
errorMessage -> errorMessageHandler.handleErrorMessage(errorMessage));
errorMessageHandler::handleErrorMessage);
}
private void createTrade(Coin amount,

View file

@ -19,12 +19,16 @@ package io.bitsquare.trade;
import com.google.inject.Singleton;
import io.bitsquare.app.AppModule;
import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.failed.FailedTradesManager;
import io.bitsquare.trade.statistics.TradeStatisticsManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import static com.google.inject.name.Names.named;
public class TradeModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(TradeModule.class);
@ -35,7 +39,9 @@ public class TradeModule extends AppModule {
@Override
protected void configure() {
bind(TradeManager.class).in(Singleton.class);
bind(TradeStatisticsManager.class).in(Singleton.class);
bind(ClosedTradableManager.class).in(Singleton.class);
bind(FailedTradesManager.class).in(Singleton.class);
bindConstant().annotatedWith(named(CoreOptionKeys.DUMP_STATISTICS)).to(env.getRequiredProperty(CoreOptionKeys.DUMP_STATISTICS));
}
}

View file

@ -18,7 +18,7 @@
package io.bitsquare.trade.closed;
import com.google.inject.Inject;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.Tradable;
@ -38,10 +38,10 @@ public class ClosedTradableManager {
private final KeyRing keyRing;
@Inject
public ClosedTradableManager(KeyRing keyRing, PriceFeed priceFeed, @Named(Storage.DIR_KEY) File storageDir) {
public ClosedTradableManager(KeyRing keyRing, PriceFeedService priceFeedService, @Named(Storage.DIR_KEY) File storageDir) {
this.keyRing = keyRing;
this.closedTrades = new TradableList<>(new Storage<>(storageDir), "ClosedTrades");
closedTrades.forEach(e -> e.getOffer().setPriceFeed(priceFeed));
closedTrades.forEach(e -> e.getOffer().setPriceFeedService(priceFeedService));
}
public void add(Tradable tradable) {

View file

@ -18,7 +18,7 @@
package io.bitsquare.trade.failed;
import com.google.inject.Inject;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.TradableList;
@ -38,10 +38,10 @@ public class FailedTradesManager {
private final KeyRing keyRing;
@Inject
public FailedTradesManager(KeyRing keyRing, PriceFeed priceFeed, @Named(Storage.DIR_KEY) File storageDir) {
public FailedTradesManager(KeyRing keyRing, PriceFeedService priceFeedService, @Named(Storage.DIR_KEY) File storageDir) {
this.keyRing = keyRing;
this.failedTrades = new TradableList<>(new Storage<>(storageDir), "FailedTrades");
failedTrades.forEach(e -> e.getOffer().setPriceFeed(priceFeed));
failedTrades.forEach(e -> e.getOffer().setPriceFeedService(priceFeedService));
}
public void add(Trade trade) {

View file

@ -21,7 +21,7 @@ import io.bitsquare.app.DevFlags;
import io.bitsquare.app.Version;
import io.bitsquare.btc.Restrictions;
import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.common.handlers.ErrorMessageHandler;
@ -144,7 +144,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
transient private OfferAvailabilityProtocol availabilityProtocol;
@JsonExclude
transient private StringProperty errorMessageProperty = new SimpleStringProperty();
transient private PriceFeed priceFeed;
@JsonExclude
transient private PriceFeedService priceFeedService;
///////////////////////////////////////////////////////////////////////////////////////////
@ -168,7 +169,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
@Nullable ArrayList<String> acceptedCountryCodes,
@Nullable String bankId,
@Nullable ArrayList<String> acceptedBankIds,
PriceFeed priceFeed) {
PriceFeedService priceFeedService) {
this.id = id;
this.offererNodeAddress = offererNodeAddress;
this.pubKeyRing = pubKeyRing;
@ -186,7 +187,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
this.acceptedCountryCodes = acceptedCountryCodes;
this.bankId = bankId;
this.acceptedBankIds = acceptedBankIds;
this.priceFeed = priceFeed;
this.priceFeedService = priceFeedService;
protocolVersion = Version.TRADE_PROTOCOL_VERSION;
@ -300,8 +301,8 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setPriceFeed(PriceFeed priceFeed) {
this.priceFeed = priceFeed;
public void setPriceFeedService(PriceFeedService priceFeedService) {
this.priceFeedService = priceFeedService;
}
public void setState(State state) {
@ -355,10 +356,10 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
@Nullable
public Fiat getPrice() {
if (useMarketBasedPrice) {
checkNotNull(priceFeed, "priceFeed must not be null");
MarketPrice marketPrice = priceFeed.getMarketPrice(currencyCode);
checkNotNull(priceFeedService, "priceFeed must not be null");
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
if (marketPrice != null) {
PriceFeed.Type priceFeedType = direction == Direction.BUY ? PriceFeed.Type.ASK : PriceFeed.Type.BID;
PriceFeedService.Type priceFeedType = direction == Direction.BUY ? PriceFeedService.Type.ASK : PriceFeedService.Type.BID;
double marketPriceAsDouble = marketPrice.getPrice(priceFeedType);
double factor = direction == Offer.Direction.BUY ? 1 - marketPriceMargin : 1 + marketPriceMargin;
double targetPrice = marketPriceAsDouble * factor;

View file

@ -17,7 +17,7 @@
package io.bitsquare.trade.offer;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.p2p.P2PService;
@ -46,7 +46,7 @@ public class OfferBookService {
}
private final P2PService p2PService;
private PriceFeed priceFeed;
private PriceFeedService priceFeedService;
private final List<OfferBookChangedListener> offerBookChangedListeners = new LinkedList<>();
@ -55,9 +55,9 @@ public class OfferBookService {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public OfferBookService(P2PService p2PService, PriceFeed priceFeed) {
public OfferBookService(P2PService p2PService, PriceFeedService priceFeedService) {
this.p2PService = p2PService;
this.priceFeed = priceFeed;
this.priceFeedService = priceFeedService;
p2PService.addHashSetChangedListener(new HashMapChangedListener() {
@Override
@ -65,7 +65,7 @@ public class OfferBookService {
offerBookChangedListeners.stream().forEach(listener -> {
if (data.getStoragePayload() instanceof Offer) {
Offer offer = (Offer) data.getStoragePayload();
offer.setPriceFeed(priceFeed);
offer.setPriceFeedService(priceFeedService);
listener.onAdded(offer);
}
});
@ -126,7 +126,7 @@ public class OfferBookService {
.filter(data -> data.getStoragePayload() instanceof Offer)
.map(data -> {
Offer offer = (Offer) data.getStoragePayload();
offer.setPriceFeed(priceFeed);
offer.setPriceFeedService(priceFeedService);
return offer;
})
.collect(Collectors.toList());

View file

@ -23,7 +23,7 @@ import io.bitsquare.app.Log;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.crypto.KeyRing;
@ -72,7 +72,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private static final long RETRY_REPUBLISH_DELAY_SEC = 10;
private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 10;
private static final long REPUBLISH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(DevFlags.STRESS_TEST_MODE ? 12 : 12);
private static final long REPUBLISH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(DevFlags.STRESS_TEST_MODE ? 14 : 14);
private static final long REFRESH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(DevFlags.STRESS_TEST_MODE ? 4 : 4);
private final KeyRing keyRing;
@ -102,7 +102,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
TradeWalletService tradeWalletService,
OfferBookService offerBookService,
ClosedTradableManager closedTradableManager,
PriceFeed priceFeed,
PriceFeedService priceFeedService,
Preferences preferences,
@Named(Storage.DIR_KEY) File storageDir) {
this.keyRing = keyRing;
@ -116,7 +116,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffersStorage = new Storage<>(storageDir);
openOffers = new TradableList<>(openOffersStorage, "OpenOffers");
openOffers.forEach(e -> e.getOffer().setPriceFeed(priceFeed));
openOffers.forEach(e -> e.getOffer().setPriceFeedService(priceFeedService));
// In case the app did get killed the shutDown from the modules is not called, so we use a shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
@ -269,7 +269,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
startPeriodicRepublishOffersTimer();
startPeriodicRefreshOffersTimer();
} else {
log.warn("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
}
}
);
@ -415,7 +415,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.info("Exception at handleRequestIsOfferAvailableMessage " + t.getMessage());
}
} else {
log.warn("We have stopped already. We ignore that handleOfferAvailabilityRequest call.");
log.debug("We have stopped already. We ignore that handleOfferAvailabilityRequest call.");
}
}
@ -432,18 +432,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
stopPeriodicRefreshOffersTimer();
for (int i = 0; i < size; i++) {
// we delay to avoid reaching throttle limits
// roughly 1 offer per second
final int n = i;
final long minDelay = i * 500 + 1;
final long maxDelay = minDelay * 2 + 500;
long delay = 500;
final long minDelay = (i + 1) * delay;
final long maxDelay = (i + 2) * delay;
final OpenOffer openOffer = openOffersList.get(i);
UserThread.runAfterRandomDelay(() -> {
OpenOffer openOffer = openOffersList.get(n);
if (openOffers.contains(openOffer))
republishOffer(openOffer);
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
}
} else {
log.warn("We have stopped already. We ignore that republishOffers call.");
log.debug("We have stopped already. We ignore that republishOffers call.");
}
}
@ -456,7 +456,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (periodicRefreshOffersTimer == null)
startPeriodicRefreshOffersTimer();
} else {
log.warn("We have stopped already. We ignore that offerBookService.republishOffers.onSuccess call.");
log.debug("We have stopped already. We ignore that offerBookService.republishOffers.onSuccess call.");
}
},
errorMessage -> {
@ -466,7 +466,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
retryRepublishOffersTimer = UserThread.runAfter(OpenOfferManager.this::republishOffers,
RETRY_REPUBLISH_DELAY_SEC);
} else {
log.warn("We have stopped already. We ignore that offerBookService.republishOffers.onFault call.");
log.debug("We have stopped already. We ignore that offerBookService.republishOffers.onFault call.");
}
});
openOffer.setStorage(openOffersStorage);
@ -480,7 +480,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (!stopped) {
republishOffers();
} else {
log.warn("We have stopped already. We ignore that periodicRepublishOffersTimer.run call.");
log.debug("We have stopped already. We ignore that periodicRepublishOffersTimer.run call.");
}
},
REPUBLISH_INTERVAL_MS,
@ -503,19 +503,20 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
final ArrayList<OpenOffer> openOffersList = new ArrayList<>(openOffers);
for (int i = 0; i < size; i++) {
// we delay to avoid reaching throttle limits
// roughly 1 offer per second
final int n = i;
final long minDelay = i * 500 + 1;
final long maxDelay = minDelay * 2 + 500;
// roughly 4 offers per second
long delay = 150;
final long minDelay = (i + 1) * delay;
final long maxDelay = (i + 2) * delay;
final OpenOffer openOffer = openOffersList.get(i);
UserThread.runAfterRandomDelay(() -> {
OpenOffer openOffer = openOffersList.get(n);
// we need to check if in the meantime the offer has been removed
if (openOffers.contains(openOffer))
refreshOffer(openOffer);
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
}
} else {
log.warn("We have stopped already. We ignore that periodicRefreshOffersTimer.run call.");
log.debug("We have stopped already. We ignore that periodicRefreshOffersTimer.run call.");
}
},
REFRESH_INTERVAL_MS,

View file

@ -17,15 +17,24 @@
package io.bitsquare.trade.protocol.availability.messages;
import io.bitsquare.app.Capabilities;
import io.bitsquare.app.Version;
import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.p2p.messaging.SupportedCapabilitiesMessage;
public final class OfferAvailabilityRequest extends OfferMessage {
import javax.annotation.Nullable;
import java.util.ArrayList;
// We add here the SupportedCapabilitiesMessage interface as that message always predates a direct connection
// to the trading peer
public final class OfferAvailabilityRequest extends OfferMessage implements SupportedCapabilitiesMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
private final PubKeyRing pubKeyRing;
public final long takersTradePrice;
@Nullable
private ArrayList<Integer> supportedCapabilities = Capabilities.getCapabilities();
public OfferAvailabilityRequest(String offerId, PubKeyRing pubKeyRing, long takersTradePrice) {
super(offerId);
@ -33,6 +42,12 @@ public final class OfferAvailabilityRequest extends OfferMessage {
this.takersTradePrice = takersTradePrice;
}
@Override
@Nullable
public ArrayList<Integer> getSupportedCapabilities() {
return supportedCapabilities;
}
public PubKeyRing getPubKeyRing() {
return pubKeyRing;
}

View file

@ -17,10 +17,18 @@
package io.bitsquare.trade.protocol.availability.messages;
import io.bitsquare.app.Capabilities;
import io.bitsquare.app.Version;
import io.bitsquare.p2p.messaging.SupportedCapabilitiesMessage;
import io.bitsquare.trade.protocol.availability.AvailabilityResult;
public final class OfferAvailabilityResponse extends OfferMessage {
import javax.annotation.Nullable;
import java.util.ArrayList;
// We add here the SupportedCapabilitiesMessage interface as that message always predates a direct connection
// to the trading peer
public final class OfferAvailabilityResponse extends OfferMessage implements SupportedCapabilitiesMessage {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
@ -28,6 +36,8 @@ public final class OfferAvailabilityResponse extends OfferMessage {
// TODO keep for backward compatibility. Can be removed once everyone is on v0.4.9
public boolean isAvailable;
@Nullable
private ArrayList<Integer> supportedCapabilities = Capabilities.getCapabilities();
public OfferAvailabilityResponse(String offerId, AvailabilityResult availabilityResult) {
super(offerId);
@ -35,6 +45,12 @@ public final class OfferAvailabilityResponse extends OfferMessage {
isAvailable = availabilityResult == AvailabilityResult.AVAILABLE;
}
@Override
@Nullable
public ArrayList<Integer> getSupportedCapabilities() {
return supportedCapabilities;
}
@Override
public String toString() {
return "OfferAvailabilityResponse{" +

View file

@ -24,6 +24,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
public abstract class OfferMessage implements DirectMessage {
//TODO add serialVersionUID also in superclasses as changes would break compatibility
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;

View file

@ -132,7 +132,8 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsOffererTrade,
() -> handleTaskRunnerSuccess("handle DepositTxPublishedMessage"),
this::handleTaskRunnerFault);
taskRunner.addTasks(ProcessDepositTxPublishedMessage.class);
taskRunner.addTasks(ProcessDepositTxPublishedMessage.class,
PublishTradeStatistics.class);
taskRunner.run();
}

View file

@ -122,7 +122,8 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
VerifyOffererAccount.class,
VerifyAndSignContract.class,
SignAndPublishDepositTxAsBuyer.class,
SendDepositTxPublishedMessage.class
SendDepositTxPublishedMessage.class,
PublishTradeStatistics.class
);
taskRunner.run();
}

View file

@ -133,7 +133,8 @@ public class SellerAsOffererProtocol extends TradeProtocol implements SellerProt
() -> handleTaskRunnerSuccess("DepositTxPublishedMessage"),
this::handleTaskRunnerFault);
taskRunner.addTasks(ProcessDepositTxPublishedMessage.class);
taskRunner.addTasks(ProcessDepositTxPublishedMessage.class,
PublishTradeStatistics.class);
taskRunner.run();
}

View file

@ -132,7 +132,8 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
VerifyOffererAccount.class,
VerifyAndSignContract.class,
SignAndPublishDepositTxAsSeller.class,
SendDepositTxPublishedMessage.class
SendDepositTxPublishedMessage.class,
PublishTradeStatistics.class
);
taskRunner.run();
}

View file

@ -24,6 +24,7 @@ import javax.annotation.concurrent.Immutable;
@Immutable
public abstract class TradeMessage implements DirectMessage {
//TODO add serialVersionUID also in superclasses as changes would break compatibility
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
@ -38,7 +39,6 @@ public abstract class TradeMessage implements DirectMessage {
TradeMessage that = (TradeMessage) o;
return !(tradeId != null ? !tradeId.equals(that.tradeId) : that.tradeId != null);
}
@Override

View file

@ -0,0 +1,47 @@
/*
* 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.protocol.trade.tasks.offerer;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
import io.bitsquare.trade.statistics.TradeStatistics;
public class PublishTradeStatistics extends TradeTask {
public PublishTradeStatistics(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
// Offerer is responsible for publishing. Only in case the offerer uses an old verison the taker publishes.
TradeStatistics tradeStatistics = new TradeStatistics(trade.getOffer(),
trade.getTradePrice(),
trade.getTradeAmount(),
trade.getDate(),
(trade.getDepositTx() != null ? trade.getDepositTx().getHashAsString() : ""),
processModel.getPubKeyRing());
processModel.getP2PService().addData(tradeStatistics, true);
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View file

@ -0,0 +1,78 @@
/*
* 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.protocol.trade.tasks.taker;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
import io.bitsquare.trade.statistics.TradeStatistics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class PublishTradeStatistics extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(PublishTradeStatistics.class);
public PublishTradeStatistics(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
// Taker only publishes if the offerer uses an old version
processModel.getP2PService().getNetworkNode().getConfirmedConnections()
.stream()
.filter(c -> c.getPeersNodeAddressOptional().isPresent() && c.getPeersNodeAddressOptional().get().equals(trade.getTradingPeerNodeAddress()))
.findAny()
.ifPresent(c -> {
TradeStatistics tradeStatistics = new TradeStatistics(trade.getOffer(),
trade.getTradePrice(),
trade.getTradeAmount(),
trade.getDate(),
(trade.getDepositTx() != null ? trade.getDepositTx().getHashAsString() : ""),
processModel.getPubKeyRing());
final List<Integer> requiredCapabilities = tradeStatistics.getRequiredCapabilities();
final List<Integer> supportedCapabilities = c.getSupportedCapabilities();
boolean matches = false;
if (supportedCapabilities != null) {
for (int messageCapability : requiredCapabilities) {
for (int connectionCapability : supportedCapabilities) {
if (messageCapability == connectionCapability) {
matches = true;
break;
}
}
}
}
if (!matches) {
log.warn("We publish tradeStatistics because the offerer uses an old version.");
processModel.getP2PService().addData(tradeStatistics, true);
} else {
log.trace("We do not publish tradeStatistics because the offerer support the capabilities.");
}
});
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View file

@ -0,0 +1,160 @@
package io.bitsquare.trade.statistics;
import io.bitsquare.app.Capabilities;
import io.bitsquare.app.Version;
import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.common.util.JsonExclude;
import io.bitsquare.p2p.storage.payload.CapabilityRequiringPayload;
import io.bitsquare.p2p.storage.payload.StoragePayload;
import io.bitsquare.trade.offer.Offer;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.Fiat;
import javax.annotation.concurrent.Immutable;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Immutable
public final class TradeStatistics implements StoragePayload, CapabilityRequiringPayload {
@JsonExclude
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
@JsonExclude
public static final long TTL = TimeUnit.DAYS.toMillis(30);
public final String currency;
public final Offer.Direction direction;
public final long tradePrice;
public final long tradeAmount;
public final long tradeDate;
public final String paymentMethod;
public final long offerDate;
public final boolean useMarketBasedPrice;
public final double marketPriceMargin;
public final long offerAmount;
public final long offerMinAmount;
public final String offerId;
public final String depositTxId;
@JsonExclude
public final PubKeyRing pubKeyRing;
public TradeStatistics(Offer offer, Fiat tradePrice, Coin tradeAmount, Date tradeDate, String depositTxId, PubKeyRing pubKeyRing) {
this.direction = offer.getDirection();
this.currency = offer.getCurrencyCode();
this.paymentMethod = offer.getPaymentMethod().getId();
this.offerDate = offer.getDate().getTime();
this.useMarketBasedPrice = offer.getUseMarketBasedPrice();
this.marketPriceMargin = offer.getMarketPriceMargin();
this.offerAmount = offer.getAmount().value;
this.offerMinAmount = offer.getMinAmount().value;
this.offerId = offer.getId();
this.tradePrice = tradePrice.longValue();
this.tradeAmount = tradeAmount.value;
this.tradeDate = tradeDate.getTime();
this.depositTxId = depositTxId;
this.pubKeyRing = pubKeyRing;
}
@Override
public long getTTL() {
return TTL;
}
@Override
public PublicKey getOwnerPubKey() {
return pubKeyRing.getSignaturePubKey();
}
@Override
public List<Integer> getRequiredCapabilities() {
return Arrays.asList(
Capabilities.Capability.TRADE_STATISTICS.ordinal()
);
}
public Date getTradeDate() {
return new Date(tradeDate);
}
public Fiat getTradePrice() {
return Fiat.valueOf(currency, tradePrice);
}
public Coin getTradeAmount() {
return Coin.valueOf(tradeAmount);
}
public Fiat getTradeVolume() {
return new ExchangeRate(getTradePrice()).coinToFiat(getTradeAmount());
}
// We don't include the pubKeyRing as both traders might publish it if the offerer uses an old
// version and update later (taker publishes first, then later offerer)
// We also don't include the trade date as that is set locally and different for offerer and taker
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TradeStatistics)) return false;
TradeStatistics that = (TradeStatistics) o;
if (tradePrice != that.tradePrice) return false;
if (tradeAmount != that.tradeAmount) return false;
if (offerDate != that.offerDate) return false;
if (useMarketBasedPrice != that.useMarketBasedPrice) return false;
if (Double.compare(that.marketPriceMargin, marketPriceMargin) != 0) return false;
if (offerAmount != that.offerAmount) return false;
if (offerMinAmount != that.offerMinAmount) return false;
if (currency != null ? !currency.equals(that.currency) : that.currency != null) return false;
if (direction != that.direction) return false;
if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null)
return false;
if (offerId != null ? !offerId.equals(that.offerId) : that.offerId != null) return false;
return !(depositTxId != null ? !depositTxId.equals(that.depositTxId) : that.depositTxId != null);
}
@Override
public int hashCode() {
int result;
long temp;
result = currency != null ? currency.hashCode() : 0;
result = 31 * result + (direction != null ? direction.hashCode() : 0);
result = 31 * result + (int) (tradePrice ^ (tradePrice >>> 32));
result = 31 * result + (int) (tradeAmount ^ (tradeAmount >>> 32));
result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0);
result = 31 * result + (int) (offerDate ^ (offerDate >>> 32));
result = 31 * result + (useMarketBasedPrice ? 1 : 0);
temp = Double.doubleToLongBits(marketPriceMargin);
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + (int) (offerAmount ^ (offerAmount >>> 32));
result = 31 * result + (int) (offerMinAmount ^ (offerMinAmount >>> 32));
result = 31 * result + (offerId != null ? offerId.hashCode() : 0);
result = 31 * result + (depositTxId != null ? depositTxId.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "TradeStatistics{" +
"currency='" + currency + '\'' +
", direction=" + direction +
", tradePrice=" + tradePrice +
", tradeAmount=" + tradeAmount +
", tradeDate=" + tradeDate +
", paymentMethod='" + paymentMethod + '\'' +
", offerDate=" + offerDate +
", useMarketBasedPrice=" + useMarketBasedPrice +
", marketPriceMargin=" + marketPriceMargin +
", offerAmount=" + offerAmount +
", offerMinAmount=" + offerMinAmount +
", offerId='" + offerId + '\'' +
", depositTxId='" + depositTxId + '\'' +
", pubKeyRing=" + pubKeyRing +
'}';
}
}

View file

@ -0,0 +1,93 @@
package io.bitsquare.trade.statistics;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.storage.HashMapChangedListener;
import io.bitsquare.p2p.storage.payload.StoragePayload;
import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry;
import io.bitsquare.storage.Storage;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
public class TradeStatisticsManager {
private static final Logger log = LoggerFactory.getLogger(TradeStatisticsManager.class);
private final Storage<HashSet<TradeStatistics>> storage;
private Storage<String> jsonStorage;
private boolean dumpStatistics;
private ObservableSet<TradeStatistics> observableTradeStatisticsSet = FXCollections.observableSet();
private HashSet<TradeStatistics> tradeStatisticsSet = new HashSet<>();
@Inject
public TradeStatisticsManager(Storage<HashSet<TradeStatistics>> storage, Storage<String> jsonStorage, P2PService p2PService, @Named(CoreOptionKeys.DUMP_STATISTICS) boolean dumpStatistics) {
this.storage = storage;
this.jsonStorage = jsonStorage;
this.dumpStatistics = dumpStatistics;
if (dumpStatistics)
this.jsonStorage.initAndGetPersistedWithFileName("trade_statistics.json");
HashSet<TradeStatistics> persisted = storage.initAndGetPersistedWithFileName("TradeStatistics");
if (persisted != null)
persisted.stream().forEach(this::add);
p2PService.addHashSetChangedListener(new HashMapChangedListener() {
@Override
public void onAdded(ProtectedStorageEntry data) {
final StoragePayload storagePayload = data.getStoragePayload();
if (storagePayload instanceof TradeStatistics)
add((TradeStatistics) storagePayload);
}
@Override
public void onRemoved(ProtectedStorageEntry data) {
// We don't remove items
}
});
}
public void add(TradeStatistics tradeStatistics) {
if (!tradeStatisticsSet.contains(tradeStatistics)) {
boolean itemAlreadyAdded = tradeStatisticsSet.stream().filter(e -> (e.offerId.equals(tradeStatistics.offerId))).findAny().isPresent();
if (!itemAlreadyAdded) {
tradeStatisticsSet.add(tradeStatistics);
observableTradeStatisticsSet.add(tradeStatistics);
storage.queueUpForSave(tradeStatisticsSet, 2000);
dump();
} else {
log.error("We have already an item with the same offer ID. That might happen if both the offerer and the taker published the tradeStatistics");
}
}
}
public ObservableSet<TradeStatistics> getObservableTradeStatisticsSet() {
return observableTradeStatisticsSet;
}
private void dump() {
if (dumpStatistics) {
// We store the statistics as json so it is easy for further processing (e.g. for web based services)
// TODO This is just a quick solution for storing to one file.
// 1 statistic entry has 500 bytes as json.
// Need a more scalable solution later when we get more volume.
// The flag will only be activated by dedicated nodes, so it should not be too critical for the moment, but needs to
// get improved. Maybe a LevelDB like DB...? Could be impl. in a headless version only.
List<TradeStatistics> list = tradeStatisticsSet.stream().collect(Collectors.toList());
list.sort((o1, o2) -> (o1.tradeDate < o2.tradeDate ? 1 : (o1.tradeDate == o2.tradeDate ? 0 : -1)));
TradeStatistics[] array = new TradeStatistics[tradeStatisticsSet.size()];
list.toArray(array);
jsonStorage.queueUpForSave(Utilities.objectToJson(array), 5_000);
}
}
}

View file

@ -25,6 +25,7 @@ import io.bitsquare.btc.FeePolicy;
import io.bitsquare.common.persistance.Persistable;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.locale.*;
import io.bitsquare.network.NetworkOptionKeys;
import io.bitsquare.storage.Storage;
import io.nucleo.net.bridge.BridgeProvider;
import javafx.beans.Observable;
@ -43,6 +44,7 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.*;
import java.util.stream.Collectors;
@ -111,8 +113,8 @@ public final class Preferences implements Persistable {
private boolean autoSelectArbitrators = true;
private final Map<String, Boolean> dontShowAgainMap;
private boolean tacAccepted;
// Don't remove as we don't want to break old serialized data
private boolean useTorForBitcoinJ = false;
private boolean useTorForHttpRequests = true;
private boolean showOwnOffersInOfferBook = true;
private Locale preferredLocale;
private TradeCurrency preferredTradeCurrency;
@ -120,8 +122,11 @@ public final class Preferences implements Persistable {
private double maxPriceDistanceInPercent;
private boolean useInvertedMarketPrice;
private String marketScreenCurrencyCode = CurrencyUtil.getDefaultTradeCurrency().getCode();
private String tradeStatisticsScreenCurrencyCode = CurrencyUtil.getDefaultTradeCurrency().getCode();
private String buyScreenCurrencyCode = CurrencyUtil.getDefaultTradeCurrency().getCode();
private String sellScreenCurrencyCode = CurrencyUtil.getDefaultTradeCurrency().getCode();
private int tradeStatisticsTickUnitIndex = 0;
private boolean useStickyMarketPrice = false;
private boolean usePercentageBasedPrice = false;
private Map<String, String> peerTagMap = new HashMap<>();
@ -134,6 +139,7 @@ public final class Preferences implements Persistable {
transient private final StringProperty btcDenominationProperty = new SimpleStringProperty(btcDenomination);
transient private final BooleanProperty useAnimationsProperty = new SimpleBooleanProperty(useAnimations);
transient private final BooleanProperty useInvertedMarketPriceProperty = new SimpleBooleanProperty(useInvertedMarketPrice);
transient private final BooleanProperty useTorForHttpRequestsProperty = new SimpleBooleanProperty(useTorForHttpRequests);
transient private final ObservableList<FiatCurrency> fiatCurrenciesAsObservable = FXCollections.observableArrayList();
transient private final ObservableList<CryptoCurrency> cryptoCurrenciesAsObservable = FXCollections.observableArrayList();
transient private final ObservableList<TradeCurrency> tradeCurrenciesAsObservable = FXCollections.observableArrayList();
@ -144,7 +150,8 @@ public final class Preferences implements Persistable {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public Preferences(Storage<Preferences> storage, BitsquareEnvironment bitsquareEnvironment) {
public Preferences(Storage<Preferences> storage, BitsquareEnvironment bitsquareEnvironment,
@Named(NetworkOptionKeys.USE_TOR_FOR_HTTP) String useTorForHttpFromOptions) {
log.debug("Preferences " + this);
INSTANCE = this;
this.storage = storage;
@ -185,8 +192,12 @@ public final class Preferences implements Persistable {
defaultLocale = preferredLocale;
preferredTradeCurrency = persisted.getPreferredTradeCurrency();
defaultTradeCurrency = preferredTradeCurrency;
// useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
useTorForBitcoinJ = false;
useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
if (useTorForHttpFromOptions.isEmpty())
setUseTorForHttpRequests(persisted.useTorForHttpRequests);
else
setUseTorForHttpRequests(useTorForHttpFromOptions.toLowerCase().equals("true"));
useStickyMarketPrice = persisted.getUseStickyMarketPrice();
usePercentageBasedPrice = persisted.getUsePercentageBasedPrice();
showOwnOffersInOfferBook = persisted.getShowOwnOffersInOfferBook();
@ -207,6 +218,8 @@ public final class Preferences implements Persistable {
marketScreenCurrencyCode = persisted.getMarketScreenCurrencyCode();
buyScreenCurrencyCode = persisted.getBuyScreenCurrencyCode();
sellScreenCurrencyCode = persisted.getSellScreenCurrencyCode();
tradeStatisticsScreenCurrencyCode = persisted.getTradeStatisticsScreenCurrencyCode();
tradeStatisticsTickUnitIndex = persisted.getTradeStatisticsTickUnitIndex();
if (persisted.getIgnoreTradersList() != null)
ignoreTradersList = persisted.getIgnoreTradersList();
@ -236,16 +249,16 @@ public final class Preferences implements Persistable {
// Use that to guarantee update of the serializable field and to make a storage update in case of a change
btcDenominationProperty.addListener((ov) -> {
btcDenomination = btcDenominationProperty.get();
storage.queueUpForSave(2000);
storage.queueUpForSave();
});
useAnimationsProperty.addListener((ov) -> {
useAnimations = useAnimationsProperty.get();
staticUseAnimations = useAnimations;
storage.queueUpForSave(2000);
storage.queueUpForSave();
});
useInvertedMarketPriceProperty.addListener((ov) -> {
useInvertedMarketPrice = useInvertedMarketPriceProperty.get();
storage.queueUpForSave(2000);
storage.queueUpForSave();
});
fiatCurrenciesAsObservable.addListener((Observable ov) -> {
fiatCurrencies.clear();
@ -257,21 +270,25 @@ public final class Preferences implements Persistable {
cryptoCurrencies.addAll(cryptoCurrenciesAsObservable);
storage.queueUpForSave();
});
useTorForHttpRequestsProperty.addListener((ov) -> {
useTorForHttpRequests = useTorForHttpRequestsProperty.get();
storage.queueUpForSave();
});
fiatCurrenciesAsObservable.addListener((ListChangeListener<FiatCurrency>) this::updateTradeCurrencies);
cryptoCurrenciesAsObservable.addListener((ListChangeListener<CryptoCurrency>) this::updateTradeCurrencies);
fiatCurrenciesAsObservable.addListener(this::updateTradeCurrencies);
cryptoCurrenciesAsObservable.addListener(this::updateTradeCurrencies);
tradeCurrenciesAsObservable.addAll(fiatCurrencies);
tradeCurrenciesAsObservable.addAll(cryptoCurrencies);
}
public void dontShowAgain(String key, boolean dontShowAgain) {
dontShowAgainMap.put(key, dontShowAgain);
storage.queueUpForSave(1000);
storage.queueUpForSave();
}
public void resetDontShowAgainForType() {
dontShowAgainMap.clear();
storage.queueUpForSave(1000);
storage.queueUpForSave();
}
@ -288,7 +305,7 @@ public final class Preferences implements Persistable {
}
public void setUseInvertedMarketPrice(boolean useInvertedMarketPrice) {
this.useInvertedMarketPriceProperty.set(useInvertedMarketPrice);
useInvertedMarketPriceProperty.set(useInvertedMarketPrice);
}
public void setBitcoinNetwork(BitcoinNetwork bitcoinNetwork) {
@ -372,10 +389,10 @@ public final class Preferences implements Persistable {
storage.queueUpForSave();
}
/* public void setUseTorForBitcoinJ(boolean useTorForBitcoinJ) {
public void setUseTorForBitcoinJ(boolean useTorForBitcoinJ) {
this.useTorForBitcoinJ = useTorForBitcoinJ;
storage.queueUpForSave();
}*/
}
public void setShowOwnOffersInOfferBook(boolean showOwnOffersInOfferBook) {
this.showOwnOffersInOfferBook = showOwnOffersInOfferBook;
@ -457,6 +474,19 @@ public final class Preferences implements Persistable {
storage.queueUpForSave();
}
public void setTradeStatisticsScreenCurrencyCode(String tradeStatisticsScreenCurrencyCode) {
this.tradeStatisticsScreenCurrencyCode = tradeStatisticsScreenCurrencyCode;
storage.queueUpForSave();
}
public void setTradeStatisticsTickUnitIndex(int tradeStatisticsTickUnitIndex) {
this.tradeStatisticsTickUnitIndex = tradeStatisticsTickUnitIndex;
storage.queueUpForSave();
}
public void setUseTorForHttpRequests(boolean useTorForHttpRequests) {
useTorForHttpRequestsProperty.set(useTorForHttpRequests);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
@ -609,6 +639,24 @@ public final class Preferences implements Persistable {
public String getDefaultPath() {
return defaultPath;
}
public String getTradeStatisticsScreenCurrencyCode() {
return tradeStatisticsScreenCurrencyCode;
}
public int getTradeStatisticsTickUnitIndex() {
return tradeStatisticsTickUnitIndex;
}
public boolean getUseTorForHttpRequests() {
return useTorForHttpRequestsProperty.get();
}
public BooleanProperty useTorForHttpRequestsProperty() {
return useTorForHttpRequestsProperty;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -18,7 +18,7 @@ public class BlockchainServiceTest {
@Test
public void testGetFee() throws InterruptedException {
BlockchainService blockchainService = new BlockchainService();
BlockchainService blockchainService = new BlockchainService(null, null, null);
// that tx has 0.001 BTC as fee
String transactionId = "38d176d0b1079b99fcb59859401d6b1679d2fa18fd8989d2c244b3682e52fce6";

View file

@ -8,14 +8,14 @@ import org.slf4j.LoggerFactory;
import static junit.framework.TestCase.assertTrue;
@Ignore
public class MarketPriceFeedTest {
private static final Logger log = LoggerFactory.getLogger(MarketPriceFeedTest.class);
public class MarketPriceFeedServiceTest {
private static final Logger log = LoggerFactory.getLogger(MarketPriceFeedServiceTest.class);
@Test
public void testGetPrice() throws InterruptedException {
PriceFeed priceFeed = new PriceFeed();
priceFeed.setCurrencyCode("EUR");
priceFeed.init(tradeCurrency -> {
PriceFeedService priceFeedService = new PriceFeedService(null, null);
priceFeedService.setCurrencyCode("EUR");
priceFeedService.init(tradeCurrency -> {
log.debug(tradeCurrency.toString());
assertTrue(true);
},

View file

@ -5,43 +5,19 @@ This guide will walk you through the process of building Bitsquare from source.
> _**NOTE:** For most users, building from source is not necessary. See the [releases page](https://github.com/bitsquare/bitsquare/releases), where you'll find installers for Windows, Linux and Mac OS X._
For the impatient
-----------------
What follows is explained in detail in the sections below, but for those who know their way around Java, git and Maven, here are the instructions in a nutshell:
$ javac -version
javac 1.8.0_66 # must be 1.8.0_66 or better
$ git clone -b FixBloomFilters https://github.com/bitsquare/bitcoinj.git
$ cd bitcoinj
$ mvn clean install -DskipTests -Dmaven.javadoc.skip=true
$ git clone https://github.com/bitsquare/bitsquare.git
$ cd bitsquare
$ mvn clean package -DskipTests
When the build completes, you will find an executable jar: `gui/target/shaded.jar`.
To run it use:
$ java -jar gui/target/shaded.jar
To build the binary check out the build scripts under the package directory.
Prerequisites
System requirements
-------------
The only prerequisite for building Bitsquare is installing the Java Development Kit (JDK), version 8u40 or better (as well as maven and git).
The prerequisite for building Bitsquare is installing the Java Development Kit (JDK), version 8u66 or better (as well as maven and git).
In Debian/Ubuntu systems with OpenJDK you'll need OpenJFX as well, i.e. you'll need the `openjfx` package besides the `openjdk-8-jdk` package.
##### 1. Check the version of Java you currently have installed
### 1. Check the version of Java you currently have installed
$ javac -version
javac 1.8.0_66
$ java -version
If `javac` is not found, or your version is anything less than `1.8.0_66`, then follow the next steps, otherwise you can skip to step 2:
If `java` is not found, or your version is anything less than `1.8.0_66`, then follow the next steps, otherwise you can skip to step 2:
###### 1.1 Debian based systems (Ubuntu)
#### 1.1 Debian based systems (Ubuntu)
To install OpenJDK use:
@ -51,8 +27,12 @@ To install the Oracle JDK use:
$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer
$ sudo apt-get -y oracle-java8-installer install
Check if $JAVA_HOME is set
$ echo $JAVA_HOME
If $JAVA_HOME is not present, add it to the .bashrc file
$ touch .bashrc
@ -60,97 +40,115 @@ If $JAVA_HOME is not present, add it to the .bashrc file
$ export JAVA_HOME=/usr/lib/jvm/java-8-oracle
$ echo $JAVA_HOME
###### 1.2 Other systems
#### 1.2 Other systems
[Download and install the latest JDK]( http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html) for your platform.
##### 2. Enable unlimited Strength for cryptographic keys
Bitsquare uses 256 bit length keys which are still not permitted by default.
Get around that ridiculous fact by adding the missing [jars from Oracle](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html).
Please follow the steps described in the Readme file at the downloaded package.
You will get an error when building Bitsquare package if you don't have these.
##### 3. Copy the BountyCastle provider jar file
Copy the BountyCastle provider jar file (bcprov-jdk15on-1.53.jar) from you local maven repository (/home/.m2/repository/org/bouncycastle/bcprov-jdk15on/1.53/bcprov-jdk15on-1.53.jar) to $JavaHome/jre/lib/ext/.
This prevent a "JCE cannot authenticate the provider BC" exception when starting the Bitsquare client.
##### 4. Edit the jre\lib\security\java.security file to add BouncyCastleProvider
Add org.bouncycastle.jce.provider.BouncyCastleProvider as last entry at: List of providers and their preference orders
E.g.:
security.provider.10=org.bouncycastle.jce.provider.BouncyCastleProvider
##### 5. Copy the jdkfix jar file
Copy the jdkfix jar file (lib/jdkfix-0.4.9.jar) from the Bitsquare src directory to $JavaHome/jre/lib/ext/.
Jdkfix.jar include a bugfix of the SortedList class which will be released with the next JDK version.
As we need to load that before the default java class we need that hack.
Steps
-----
### 1. Get the source
The preferred approach is to clone the Bitsquare repository using [git](http://www.git-scm.com/):
git clone https://github.com/bitsquare/bitsquare.git
However, if you're not familiar with git or it is otherwise inconvenient to use, you can also download and extract a zip file of the latest sources at https://github.com/bitsquare/bitsquare/archive/master.zip.
Build bitcoinj
-----------------
### 2. Install bitcoinj fork
Versions later than 0.13.1 has removed support for Java serialisation.
> _**NOTE:**
Bitcoinj versions later than 0.13.1 has removed support for Java serialisation.
In version 0.13.1 is also missing support for Java serialisation in MainNetParams (HttpDiscovery.Details).
We removed usage of Cartographer/HttpDiscovery and fixed privacy issues with Bloom Filters at our [fork version 0.13.1.4](https://github.com/bitsquare/bitcoinj/tree/FixBloomFilters).
We removed usage of Cartographer/HttpDiscovery and fixed privacy issues with Bloom Filters at our [fork version 0.13.1.5](https://github.com/bitsquare/bitcoinj/tree/FixBloomFilters).
Beside the Java serialisation issues there are [privacy concerns](http://bitcoin-development.narkive.com/hczWIAby/bitcoin-development-cartographer#post3) regarding Cartographer.
Here is a Github issue with background and open tasks regarding [Bloom Filters](https://github.com/bitsquare/bitsquare/issues/414).
Here is a Github issue with background and open tasks regarding [Bloom Filters](https://github.com/bitsquare/bitsquare/issues/414)._
$ git clone -b FixBloomFilters https://github.com/bitsquare/bitcoinj.git
$ cd bitcoinj
$ mvn clean install -DskipTests -Dmaven.javadoc.skip=true
### 3. Build jar
Prepare Bitsquare build
-----------------
Bitsquare uses maven as a build system.
### 3. Get Bitsquare source code and build a preliminary Bitsquare version
You need to get the Bitsquare dependencies first as we need to copy the BountyCastle jar to the JRE directory as well as the jdkfix jar.
$ git clone https://github.com/bitsquare/bitsquare.git
$ cd bitsquare
$ mvn clean package -DskipTests
$ mvn clean package -DskipTests -Dmaven.javadoc.skip=true
### 4. Copy the jdkfix jar file
Copy the jdkfix-0.4.9.1.jar from the Bitsquare jdkfix/target directory to $JAVA_HOME/jre/lib/ext/.
jdkfix-0.4.9.1.jar includes a bugfix of the SortedList class which will be released with the next JDK version.
We need to load that class before the default java class. This step will be removed once the bugfix is in the official JDK.
$ sudo cp bitsquare/jdkfix/target/jdkfix-0.4.9.1.jar $JAVA_HOME/jre/lib/ext/jdkfix-0.4.9.1.jar
### 4. Run
### 5. Copy the BountyCastle provider jar file
Copy the BountyCastle provider jar file from the local maven repository to the jre/lib/ext directory.
This prevents a "JCE cannot authenticate the provider BC" exception when starting the Bitsquare client.
$ sudo cp ~/.m2/repository/org/bouncycastle/bcprov-jdk15on/1.53/bcprov-jdk15on-1.53.jar $JAVA_HOME/jre/lib/ext/bcprov-jdk15on-1.53.jar
### 6. Edit the java.security file and add BouncyCastleProvider
Add org.bouncycastle.jce.provider.BouncyCastleProvider as last entry at: List of providers and their preference orders
E.g.:
security.provider.10=org.bouncycastle.jce.provider.BouncyCastleProvider
$ sudo gedit $JAVA_HOME/jre/lib/security/java.security
... edit and save
### 7. Enable unlimited Strength for cryptographic keys (if Oracle JDK is used)
If you are using Oracle JDK you need to follow the following step. If you use OpenJDK + OpenJFX you can skip that step.
Bitsquare uses 256 bit length keys which are still not permitted by default.
Get around that ridiculous fact by adding the missing [jars from Oracle](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html).
Please follow the steps described in the Readme file at the downloaded package.
You will get an error when building Bitsquare package if you don't have these.
$ wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip
$ unzip jce_policy-8.zip
$ sudo cp UnlimitedJCEPolicyJDK8/US_export_policy.jar $JAVA_HOME/jre/lib/security/US_export_policy.jar
$ sudo cp UnlimitedJCEPolicyJDK8/local_policy.jar $JAVA_HOME/jre/lib/security/local_policy.jar
Build Bitsquare
-----------------
### 8. Build final Bitsquare jar
Now we have all prepared to build the correct Bitsquare jar.
$ mvn clean package -DskipTests -Dmaven.javadoc.skip=true
When the build completes, you will find an executable jar: `gui/target/shaded.jar`.
To run it use:
$ java -jar gui/target/shaded.jar
Please note that testnet is the default bitcoin network.
### 5. Development mode
Build binaries
-----------------
If you want to build the binaryies check out the build scripts under the package directory.
Development mode
-----------------
Please check out our wiki for more information about [testing](https://github.com/bitsquare/bitsquare/wiki/Testing-Bitsquare-with-Testnet)
Please check out our wiki for more information about [testing](https://github.com/bitsquare/bitsquare/wiki/Testing-Bitsquare-with-Mainnet)
and how to use [regtest](https://github.com/bitsquare/bitsquare/wiki/How-to-use-Bitsquare-with-regtest-%28advanced%29)
Here are example program arguments for using regtest with localhost environment (not via Tor):
Here are example program arguments for using regtest with localhost environment (not using Tor):
$ java -jar seednode/target/SeedNode.jar --useLocalhost=true --nodePort=2002 --bitcoinNetwork=regtest
$ java -jar seednode/target/SeedNode.jar --bitcoinNetwork=REGTEST --useLocalhost=true --myAddress=localhost:2002 --nodePort=2002 --appName=Bitsquare_seed_node_localhost_2002
$ java -jar gui/target/shaded.jar --useLocalhost=true --nodePort=2222 --devTest=true --appName=Bitsquare-Local-Regtest-Arbitrator
$ java -jar gui/target/shaded.jar --bitcoinNetwork=REGTEST --useLocalhost=true --myAddress=localhost:2222 --nodePort=2222 --appName=Bitsquare-Local-Regtest-Arbitrator
$ java -jar gui/target/shaded.jar --bitcoinNetwork=regtest --nodePort=3332 --useLocalhost=true --devTest=true --appName=Bitsquare-Local-Regtest-Alice
$ java -jar gui/target/shaded.jar --bitcoinNetwork=REGTEST --useLocalhost=true --myAddress=localhost:3333 --nodePort=3333 --appName=Bitsquare-Local-Regtest-Alice
$ java -jar gui/target/shaded.jar --bitcoinNetwork=regtest --nodePort=4442 --useLocalhost=true --devTest=true --appName=Bitsquare-Local-Regtest-Bob
$ java -jar gui/target/shaded.jar --bitcoinNetwork=REGTEST --useLocalhost=true --myAddress=localhost:4444 --nodePort=4444 --appName=Bitsquare-Local-Regtest-Bob
### 6. Running local seed node with Tor
Running local seed node with Tor and RegTest
-----------------
If you want to run locally a seed node via Tor you need to add your seed node's hidden service address to the SeedNodesRepository.java class.
You can find the hidden service address after you started once a seed node. Start it with a placeholder address like:
$ java -jar seednode/target/SeedNode.jar --bitcoinNetwork=REGTEST --myAddress=xxxxxxx.onion:8002 --appName=Bitsquare_seed_node_xxxxxxx.onion_8002 --nodePort=8002
$ java -jar seednode/target/SeedNode.jar --bitcoinNetwork=REGTEST --nodePort=8002 --myAddress=xxxxxxxx.onion:8002 --appName=Bitsquare_seed_node_xxxxxxxx.onion_8000
Once the hidden service is published (check console output) quit the seed node and copy the hidden service address from the console output.
Alternatively you can navigate to the application directory and open Bitsquare_seed_node_xxxxxxx.onion_8002/tor/hiddenservice/hostname.
@ -158,15 +156,18 @@ use that hidden service address also to rename the xxxxxxx placeholder of your B
Start again the SeedNode.jar now with the correct hidden service address.
Instructions are also at the SeedNodesRepository class.
Here are example program arguments for using regtest and using the Tor network:
Here are example program arguments for using regtest and using the Tor network (example onion address is ewdkppp3vicnbgqt):
$ java -jar seednode/target/SeedNode.jar --bitcoinNetwork=REGTEST --myAddress=rxdkppp3vicnbgqt.onion:8002 --appName=Bitsquare_seed_node_rxdkppp3vicnbgqt.onion_8002 --nodePort=8002
$ java -jar seednode/target/SeedNode.jar ewdkppp3vicnbgqt.onion:8002 2 50
$ java -jar gui/target/shaded.jar --bitcoinNetwork=regtest nodePort=2222 --devTest=true --appName=Bitsquare-Tor-Regtest-Arbitrator
$ java -jar gui/target/shaded.jar --bitcoinNetwork=regtest nodePort=3332 --devTest=true --appName=Bitsquare-Tor-Regtest-Alice
$ java -jar gui/target/shaded.jar --bitcoinNetwork=regtest nodePort=4442 --devTest=true --appName=Bitsquare-Tor-Regtest-Bob
$ java -jar seednode/target/SeedNode.jar --bitcoinNetwork=REGTEST --nodePort=8002 --myAddress=ewdkppp3vicnbgqt.onion:8002 --appName=Bitsquare_seed_node_ewdkppp3vicnbgqt.oinion_8002
$ java -jar gui/target/shaded.jar --bitcoinNetwork=REGTEST --myAddress=localhost:2222 --nodePort=2222 --appName=Bitsquare-Local-Regtest-Arbitrator
$ java -jar gui/target/shaded.jar --bitcoinNetwork=REGTEST --myAddress=localhost:3333 --nodePort=3333 --appName=Bitsquare-Local-Regtest-Alice
$ java -jar gui/target/shaded.jar --bitcoinNetwork=REGTEST --myAddress=localhost:4444 --nodePort=4444 --appName=Bitsquare-Local-Regtest-Bob
Problems?
---------

View file

@ -22,7 +22,7 @@
<parent>
<artifactId>parent</artifactId>
<groupId>io.bitsquare</groupId>
<version>0.4.9</version>
<version>0.4.9.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View file

@ -24,7 +24,7 @@ import com.google.inject.Injector;
import io.bitsquare.alert.AlertManager;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.OptionKeys;
import io.bitsquare.common.CommonOptionKeys;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.common.util.Utilities;
@ -45,6 +45,7 @@ import io.bitsquare.gui.main.overlays.windows.SendAlertMessageWindow;
import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.offer.OpenOfferManager;
import javafx.application.Application;
import javafx.application.Platform;
@ -77,7 +78,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static io.bitsquare.app.BitsquareEnvironment.APP_NAME_KEY;
import static io.bitsquare.app.CoreOptionKeys.APP_NAME_KEY;
public class BitsquareApp extends Application {
private static final Logger log = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(BitsquareApp.class);
@ -104,12 +105,12 @@ public class BitsquareApp extends Application {
public void start(Stage stage) throws IOException {
BitsquareApp.primaryStage = stage;
String logPath = Paths.get(env.getProperty(BitsquareEnvironment.APP_DATA_DIR_KEY), "bitsquare").toString();
String logPath = Paths.get(env.getProperty(CoreOptionKeys.APP_DATA_DIR_KEY), "bitsquare").toString();
Log.setup(logPath);
log.info("Log files under: " + logPath);
Version.printVersion();
Utilities.printSysInfo();
Log.setLevel(Level.toLevel(env.getRequiredProperty(OptionKeys.LOG_LEVEL_KEY)));
Log.setLevel(Level.toLevel(env.getRequiredProperty(CommonOptionKeys.LOG_LEVEL_KEY)));
UserThread.setExecutor(Platform::runLater);
UserThread.setTimerClass(UITimer.class);
@ -174,7 +175,8 @@ public class BitsquareApp extends Application {
Font.loadFont(getClass().getResource("/fonts/VerdanaBoldItalic.ttf").toExternalForm(), 13);
scene.getStylesheets().setAll(
"/io/bitsquare/gui/bitsquare.css",
"/io/bitsquare/gui/images.css");
"/io/bitsquare/gui/images.css",
"/io/bitsquare/gui/CandleStickChart.css");
// configure the system tray
SystemTray.create(primaryStage, shutDownHandler);
@ -346,20 +348,21 @@ public class BitsquareApp extends Application {
@Override
public void stop() {
shutDownRequested = true;
new Popup().headLine("Shut down in progress")
.backgroundInfo("Shutting down application can take a few seconds.\n" +
"Please don't interrupt that process.")
.hideCloseButton()
.useAnimation(false)
.show();
UserThread.runAfter(() -> {
gracefulShutDown(() -> {
log.info("App shutdown complete");
System.exit(0);
});
}, 200, TimeUnit.MILLISECONDS);
if (!shutDownRequested) {
new Popup().headLine("Shut down in progress")
.backgroundInfo("Shutting down application can take a few seconds.\n" +
"Please don't interrupt that process.")
.hideCloseButton()
.useAnimation(false)
.show();
UserThread.runAfter(() -> {
gracefulShutDown(() -> {
log.info("App shutdown complete");
System.exit(0);
});
}, 200, TimeUnit.MILLISECONDS);
shutDownRequested = true;
}
}
private void gracefulShutDown(ResultHandler resultHandler) {
@ -368,6 +371,7 @@ public class BitsquareApp extends Application {
if (injector != null) {
injector.getInstance(ArbitratorManager.class).shutDown();
injector.getInstance(MainViewModel.class).shutDown();
injector.getInstance(TradeManager.class).shutDown();
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
injector.getInstance(P2PService.class).shutDown(() -> {
injector.getInstance(WalletService.class).shutDownDone.addListener((ov, o, n) -> {
@ -378,8 +382,8 @@ public class BitsquareApp extends Application {
injector.getInstance(WalletService.class).shutDown();
});
});
// we wait max 5 sec.
UserThread.runAfter(resultHandler::handleResult, 5);
// we wait max 20 sec.
UserThread.runAfter(resultHandler::handleResult, 20);
} else {
UserThread.runAfter(resultHandler::handleResult, 1);
}

View file

@ -23,7 +23,8 @@ import joptsimple.OptionSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.bitsquare.app.BitsquareEnvironment.*;
import static io.bitsquare.app.BitsquareEnvironment.DEFAULT_APP_NAME;
import static io.bitsquare.app.BitsquareEnvironment.DEFAULT_USER_DATA_DIR;
public class BitsquareAppMain extends BitsquareExecutable {
private static final Logger log = LoggerFactory.getLogger(BitsquareAppMain.class);
@ -33,9 +34,9 @@ public class BitsquareAppMain extends BitsquareExecutable {
// So we only handle the absolute minimum which is APP_NAME, APP_DATA_DIR_KEY and USER_DATA_DIR
OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
parser.accepts(USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
parser.accepts(CoreOptionKeys.USER_DATA_DIR_KEY, description("User data directory", DEFAULT_USER_DATA_DIR))
.withRequiredArg();
parser.accepts(APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME))
parser.accepts(CoreOptionKeys.APP_NAME_KEY, description("Application name", DEFAULT_APP_NAME))
.withRequiredArg();
OptionSet options;
@ -51,7 +52,7 @@ public class BitsquareAppMain extends BitsquareExecutable {
BitsquareEnvironment bitsquareEnvironment = new BitsquareEnvironment(options);
// need to call that before BitsquareAppMain().execute(args)
BitsquareExecutable.initAppDir(bitsquareEnvironment.getProperty(BitsquareEnvironment.APP_DATA_DIR_KEY));
BitsquareExecutable.initAppDir(bitsquareEnvironment.getProperty(CoreOptionKeys.APP_DATA_DIR_KEY));
// For some reason the JavaFX launch process results in us losing the thread context class loader: reset it.
// In order to work around a bug in JavaFX 8u25 and below, you must include the following code as the first line of your realMain method:

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2008, 2013 Oracle and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
* This file is available and licensed under the following license:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution.
* - Neither the name of Oracle Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* ====== CANDLE STICK CHART =========================================================== */
.candlestick-tooltip-label {
-fx-font-size: 0.75em;
-fx-font-weight: bold;
-fx-text-fill: #666666;
-fx-padding: 2 5 2 0;
}
.candlestick-average-line {
-fx-stroke: #00b2ff;
-fx-stroke-width: 2px;
}
.candlestick-candle {
-fx-effect: dropshadow(two-pass-box, rgba(0, 0, 0, 0.4), 10, 0.0, 2, 4);
}
.candlestick-line {
-fx-stroke: #666666;
-fx-stroke-width: 3px;
}
.candlestick-bar {
-fx-padding: 5;
-demo-bar-fill: #e81a00;
-fx-background-color: linear-gradient(derive(-demo-bar-fill, -30%), derive(-demo-bar-fill, -40%)),
linear-gradient(derive(-demo-bar-fill, 100%), derive(-demo-bar-fill, 10%)),
linear-gradient(derive(-demo-bar-fill, 30%), derive(-demo-bar-fill, -10%));
-fx-background-insets: 0, 1, 2;
}
.candlestick-bar.close-above-open {
-demo-bar-fill: #1bff06;
}
.candlestick-bar.open-above-close {
-demo-bar-fill: #e81a00;
}
.candlestick-bar.empty {
-demo-bar-fill: #cccccc;
}
.volume-bar {
-fx-padding: 5;
-demo-bar-fill: #91b1cc;
-fx-background-color: #91b1cc;
-fx-background-insets: 0, 1, 2;
}
.volume-bar.bg {
-demo-bar-fill: #70bfc6;
}
.volume-bar {
-fx-effect: dropshadow(two-pass-box, rgba(0, 0, 0, 0.4), 10, 0.0, 2, 4);
}
.chart-alternative-row-fill {
-fx-fill: transparent;
-fx-stroke: transparent;
-fx-stroke-width: 0;
}
.chart-plot-background {
-fx-background-color: transparent;
}

View file

@ -20,7 +20,7 @@ package io.bitsquare.gui;
import com.google.inject.Singleton;
import com.google.inject.name.Names;
import io.bitsquare.app.AppModule;
import io.bitsquare.app.BitsquareEnvironment;
import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.gui.common.fxml.FxmlViewLoader;
import io.bitsquare.gui.common.view.CachingViewLoader;
import io.bitsquare.gui.common.view.ViewFactory;
@ -69,6 +69,6 @@ public class GuiModule extends AppModule {
bind(Stage.class).toInstance(primaryStage);
bindConstant().annotatedWith(Names.named(MainView.TITLE_KEY)).to(env.getRequiredProperty(BitsquareEnvironment.APP_NAME_KEY));
bindConstant().annotatedWith(Names.named(MainView.TITLE_KEY)).to(env.getRequiredProperty(CoreOptionKeys.APP_NAME_KEY));
}
}

View file

@ -27,7 +27,7 @@ bg color of non edit textFields: fafafa
-bs-content-bg-grey: #f4f4f4;
-bs-very-light-grey: #f8f8f8;
-fx-accent: #0f87c3;
-fx-accent: #0f86c3;
-bs-blue-soft: derive(-fx-accent, 60%);
-bs-blue-transparent: #0f87c344;
@ -1069,4 +1069,35 @@ textfield */
-fx-alignment: center;
-fx-font-size: 10;
-fx-text-fill: white;
}
}
#toggle-left {
-fx-border-radius: 4 0 0 4;
-fx-padding: 4 4 4 4;
-fx-border-color: #aaa;
-fx-border-style: solid solid solid solid;
-fx-border-insets: 0 -2 0 0;
-fx-background-insets: 0 -2 0 0;
-fx-background-radius: 4 0 0 4;
}
#toggle-center {
-fx-border-radius: 0 0 0 0;
-fx-padding: 4 4 4 4;
-fx-border-color: #aaa;
-fx-border-style: solid solid solid solid;
-fx-border-insets: 0 0 0 0;
-fx-background-insets: 0 0 0 0;
-fx-background-radius: 0 0 0 0;
}
#toggle-right {
-fx-border-radius: 0 4 4 0;
-fx-padding: 4 4 4 4;
-fx-border-color: #aaa;
-fx-border-style: solid solid solid solid;
-fx-border-insets: 0 0 0 -2;
-fx-background-insets: 0 0 0 -2;
-fx-background-radius: 0 4 4 0;
}

View file

@ -30,7 +30,7 @@ public class SearchComboBox<T> extends ComboBox<T> {
findAny().isPresent()) {
UserThread.execute(() -> {
filteredList.setPredicate(item -> newValue.isEmpty() ||
getConverter().toString(item).toLowerCase().startsWith(newValue.toLowerCase()));
getConverter().toString(item).toLowerCase().contains(newValue.toLowerCase()));
hide();
setVisibleRowCount(Math.min(20, filteredList.size()));
show();

View file

@ -20,7 +20,7 @@ package io.bitsquare.gui.main;
import io.bitsquare.BitsquareException;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.app.DevFlags;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Tuple2;
import io.bitsquare.common.util.Tuple3;
@ -158,7 +158,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
marketPriceBox.second.textProperty().bind(createStringBinding(
() -> {
PriceFeed.Type type = model.typeProperty.get();
PriceFeedService.Type type = model.typeProperty.get();
return type != null ? "Market price (" + type.name + ")" : "";
},
model.marketPriceCurrencyCode, model.typeProperty));

View file

@ -34,7 +34,7 @@ import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.Clock;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
@ -120,7 +120,7 @@ public class MainViewModel implements ViewModel {
final StringProperty walletServiceErrorMsg = new SimpleStringProperty();
final StringProperty btcSplashSyncIconId = new SimpleStringProperty();
final StringProperty marketPriceCurrencyCode = new SimpleStringProperty("");
final ObjectProperty<PriceFeed.Type> typeProperty = new SimpleObjectProperty<>(PriceFeed.Type.LAST);
final ObjectProperty<PriceFeedService.Type> typeProperty = new SimpleObjectProperty<>(PriceFeedService.Type.LAST);
final ObjectProperty<PriceFeedComboBoxItem> selectedPriceFeedComboBoxItemProperty = new SimpleObjectProperty<>();
final BooleanProperty isFiatCurrencyPriceFeedSelected = new SimpleBooleanProperty(true);
final BooleanProperty isCryptoCurrencyPriceFeedSelected = new SimpleBooleanProperty(false);
@ -153,7 +153,7 @@ public class MainViewModel implements ViewModel {
final StringProperty p2pNetworkLabelId = new SimpleStringProperty("footer-pane");
private MonadicBinding<Boolean> allServicesDone, tradesAndUIReady;
final PriceFeed priceFeed;
final PriceFeedService priceFeedService;
private final User user;
private int numBtcPeers = 0;
private Timer checkNumberOfBtcPeersTimer;
@ -164,7 +164,7 @@ public class MainViewModel implements ViewModel {
private Subscription priceFeedAllLoadedSubscription;
private Popup startupTimeoutPopup;
private BooleanProperty p2pNetWorkReady;
private BooleanProperty walletInitialized;
private final BooleanProperty walletInitialized = new SimpleBooleanProperty();
///////////////////////////////////////////////////////////////////////////////////////////
@ -173,14 +173,14 @@ public class MainViewModel implements ViewModel {
@Inject
public MainViewModel(WalletService walletService, TradeWalletService tradeWalletService,
PriceFeed priceFeed,
PriceFeedService priceFeedService,
ArbitratorManager arbitratorManager, P2PService p2PService, TradeManager tradeManager,
OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences,
User user, AlertManager alertManager, PrivateNotificationManager privateNotificationManager,
FilterManager filterManager, WalletPasswordWindow walletPasswordWindow,
NotificationCenter notificationCenter, TacWindow tacWindow, Clock clock,
KeyRing keyRing, Navigation navigation, BSFormatter formatter) {
this.priceFeed = priceFeed;
this.priceFeedService = priceFeedService;
this.user = user;
this.walletService = walletService;
this.tradeWalletService = tradeWalletService;
@ -234,8 +234,8 @@ public class MainViewModel implements ViewModel {
showStartupTimeoutPopup();
}, 4, TimeUnit.MINUTES);
walletInitialized = initBitcoinWallet();
p2pNetWorkReady = initP2PNetwork();
initBitcoinWallet();
// need to store it to not get garbage collected
allServicesDone = EasyBind.combine(walletInitialized, p2pNetWorkReady, (a, b) -> a && b);
@ -283,6 +283,7 @@ public class MainViewModel implements ViewModel {
///////////////////////////////////////////////////////////////////////////////////////////
private BooleanProperty initP2PNetwork() {
Log.traceCall();
StringProperty bootstrapState = new SimpleStringProperty();
StringProperty bootstrapWarning = new SimpleStringProperty();
BooleanProperty hiddenServicePublished = new SimpleBooleanProperty();
@ -348,6 +349,9 @@ public class MainViewModel implements ViewModel {
public void onTorNodeReady() {
bootstrapState.set("Tor node created");
p2PNetworkIconId.set("image-connection-tor");
if (preferences.getUseTorForBitcoinJ())
initWalletService();
}
@Override
@ -421,7 +425,17 @@ public class MainViewModel implements ViewModel {
return p2pNetworkInitialized;
}
private BooleanProperty initBitcoinWallet() {
private void initBitcoinWallet() {
Log.traceCall();
// We only init wallet service here if not using Tor for bitcoinj.
// When using Tor, wallet init must be deferred until Tor is ready.
if (!preferences.getUseTorForBitcoinJ())
initWalletService();
}
private void initWalletService() {
Log.traceCall();
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
btcInfoBinding = EasyBind.combine(walletService.downloadPercentageProperty(), walletService.numPeersProperty(), walletServiceException,
(downloadPercentage, numPeers, exception) -> {
@ -462,7 +476,6 @@ public class MainViewModel implements ViewModel {
btcInfo.set(newValue);
});
final BooleanProperty walletInitialized = new SimpleBooleanProperty();
walletService.initialize(null,
() -> {
numBtcPeers = walletService.numPeersProperty().get();
@ -484,7 +497,6 @@ public class MainViewModel implements ViewModel {
}
},
walletServiceException::set);
return walletInitialized;
}
private void onAllServicesInitialized() {
@ -738,11 +750,11 @@ public class MainViewModel implements ViewModel {
}
private void setupMarketPriceFeed() {
if (priceFeed.getCurrencyCode() == null)
priceFeed.setCurrencyCode(preferences.getPreferredTradeCurrency().getCode());
if (priceFeed.getType() == null)
priceFeed.setType(PriceFeed.Type.LAST);
priceFeed.init(price -> {
if (priceFeedService.getCurrencyCode() == null)
priceFeedService.setCurrencyCode(preferences.getPreferredTradeCurrency().getCode());
if (priceFeedService.getType() == null)
priceFeedService.setType(PriceFeedService.Type.LAST);
priceFeedService.init(price -> {
marketPrice.set(formatter.formatMarketPrice(price));
marketPriceInverted.set(price != 0 ? formatter.formatMarketPrice(1 / price, 8) : "");
},
@ -750,8 +762,8 @@ public class MainViewModel implements ViewModel {
marketPrice.set("N/A");
marketPriceInverted.set("N/A");
});
marketPriceCurrencyCode.bind(priceFeed.currencyCodeProperty());
typeProperty.bind(priceFeed.typeProperty());
marketPriceCurrencyCode.bind(priceFeedService.currencyCodeProperty());
typeProperty.bind(priceFeedService.typeProperty());
marketPriceBinding = EasyBind.combine(
marketPriceCurrencyCode, marketPrice, marketPriceInverted, preferences.useInvertedMarketPriceProperty(),
@ -765,7 +777,7 @@ public class MainViewModel implements ViewModel {
String code = preferences.getUseStickyMarketPrice() ?
preferences.getPreferredTradeCurrency().getCode() :
priceFeed.currencyCodeProperty().get();
priceFeedService.currencyCodeProperty().get();
Optional<PriceFeedComboBoxItem> itemOptional = findPriceFeedComboBoxItem(code);
if (itemOptional.isPresent()) {
if (selectedPriceFeedComboBoxItemProperty.get() == null || !preferences.getUseStickyMarketPrice()) {
@ -791,7 +803,7 @@ public class MainViewModel implements ViewModel {
}
});
priceFeedAllLoadedSubscription = EasyBind.subscribe(priceFeed.currenciesUpdateFlagProperty(), newPriceUpdate -> setMarketPriceInItems());
priceFeedAllLoadedSubscription = EasyBind.subscribe(priceFeedService.currenciesUpdateFlagProperty(), newPriceUpdate -> setMarketPriceInItems());
preferences.getTradeCurrenciesAsObservable().addListener((ListChangeListener<TradeCurrency>) c -> {
UserThread.runAfter(() -> {
@ -804,12 +816,12 @@ public class MainViewModel implements ViewModel {
private void setMarketPriceInItems() {
priceFeedComboBoxItems.stream().forEach(item -> {
String currencyCode = item.currencyCode;
MarketPrice marketPrice = priceFeed.getMarketPrice(currencyCode);
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
boolean useInvertedMarketPrice = preferences.getUseInvertedMarketPrice();
String priceString;
String currencyPairString = useInvertedMarketPrice ? "BTC/" + currencyCode : currencyCode + "/BTC";
if (marketPrice != null) {
double price = marketPrice.getPrice(priceFeed.getType());
double price = marketPrice.getPrice(priceFeedService.getType());
if (price != 0) {
double priceInverted = 1 / price;
priceString = useInvertedMarketPrice ? formatter.formatMarketPrice(priceInverted, 8) : formatter.formatMarketPrice(price);
@ -828,7 +840,7 @@ public class MainViewModel implements ViewModel {
public void setPriceFeedComboBoxItem(PriceFeedComboBoxItem item) {
if (!preferences.getUseStickyMarketPrice() && item != null) {
Optional<PriceFeedComboBoxItem> itemOptional = findPriceFeedComboBoxItem(priceFeed.currencyCodeProperty().get());
Optional<PriceFeedComboBoxItem> itemOptional = findPriceFeedComboBoxItem(priceFeedService.currencyCodeProperty().get());
if (itemOptional.isPresent())
selectedPriceFeedComboBoxItemProperty.set(itemOptional.get());
else
@ -836,7 +848,7 @@ public class MainViewModel implements ViewModel {
.ifPresent(item2 -> selectedPriceFeedComboBoxItemProperty.set(item2));
} else if (item != null) {
selectedPriceFeedComboBoxItemProperty.set(item);
priceFeed.setCurrencyCode(item.currencyCode);
priceFeedService.setCurrencyCode(item.currencyCode);
} else {
findPriceFeedComboBoxItem(preferences.getPreferredTradeCurrency().getCode())
.ifPresent(item2 -> selectedPriceFeedComboBoxItemProperty.set(item2));

View file

@ -167,37 +167,17 @@ public class AltCoinAccountsView extends ActivatableViewAndModel<GridPane, AltCo
"If you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.")
.closeButtonText("I understand")
.show();
} else if (code.equals("ETHC")) {
//TODO remove after JULY, 21
if (new Date().before(new Date(2016 - 1900, Calendar.JULY, 21))) {
new Popup().information("You cannot use EtherClassic before the hard fork gets activated.\n" +
"It is planned for July, 20 2016, but please check on their project web page for detailed information.\n\n" +
"The EHT/ETHC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://ethereumclassic.github.io/"))
.actionButtonText("Open Ethereum Classic web page")
.show();
} else if (new Date().before(new Date(2016 - 1900, Calendar.AUGUST, 30))) {
//TODO remove after AUGUST, 30
new Popup().information("The EHT/ETHC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://ethereumclassic.github.io/"))
.actionButtonText("Open Ethereum Classic web page")
.show();
}
} else if (code.equals("ETH")) {
} else if (code.equals("ETC")) {
//TODO remove after AUGUST, 30
if (new Date().before(new Date(2016 - 1900, Calendar.AUGUST, 30))) {
new Popup().information("The EHT/ETHC fork situation carries considerable risks.\n" +
new Popup().information("The EHT/ETC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://www.ethereum.org/"))
.actionButtonText("Open Ethereum web page")
.onAction(() -> Utilities.openWebPage("https://ethereumclassic.github.io/"))
.actionButtonText("Open Ethereum Classic web page")
.show();
}
}
}
if (!model.getPaymentAccounts().stream().filter(e -> {
if (e.getAccountName() != null)

View file

@ -18,6 +18,7 @@
package io.bitsquare.gui.main.account.content.backup;
import io.bitsquare.app.BitsquareEnvironment;
import io.bitsquare.app.CoreOptionKeys;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.gui.common.view.ActivatableView;
@ -62,7 +63,7 @@ public class BackupView extends ActivatableView<GridPane, Void> {
super();
this.stage = stage;
this.preferences = preferences;
dataDir = new File(environment.getProperty(BitsquareEnvironment.APP_DATA_DIR_KEY));
dataDir = new File(environment.getProperty(CoreOptionKeys.APP_DATA_DIR_KEY));
}
@Override

View file

@ -27,4 +27,5 @@
<Tab fx:id="chartsTab" text="Offer book" closable="false"/>
<Tab fx:id="statisticsTab" text="Spreads" closable="false"/>
<Tab fx:id="tradesTab" text="Trades" closable="false"/>
</TabPane>

View file

@ -23,6 +23,7 @@ import io.bitsquare.gui.common.view.*;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.markets.charts.MarketsChartsView;
import io.bitsquare.gui.main.markets.statistics.MarketsStatisticsView;
import io.bitsquare.gui.main.markets.trades.TradesChartsView;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.Tab;
@ -33,7 +34,7 @@ import javax.inject.Inject;
@FxmlView
public class MarketView extends ActivatableViewAndModel<TabPane, Activatable> {
@FXML
Tab chartsTab, statisticsTab;
Tab chartsTab, tradesTab, statisticsTab;
private final ViewLoader viewLoader;
private final Navigation navigation;
private Navigation.Listener navigationListener;
@ -55,6 +56,8 @@ public class MarketView extends ActivatableViewAndModel<TabPane, Activatable> {
tabChangeListener = (ov, oldValue, newValue) -> {
if (newValue == chartsTab)
navigation.navigateTo(MainView.class, MarketView.class, MarketsChartsView.class);
else if (newValue == tradesTab)
navigation.navigateTo(MainView.class, MarketView.class, TradesChartsView.class);
else if (newValue == statisticsTab)
navigation.navigateTo(MainView.class, MarketView.class, MarketsStatisticsView.class);
};
@ -67,6 +70,8 @@ public class MarketView extends ActivatableViewAndModel<TabPane, Activatable> {
if (root.getSelectionModel().getSelectedItem() == chartsTab)
navigation.navigateTo(MainView.class, MarketView.class, MarketsChartsView.class);
else if (root.getSelectionModel().getSelectedItem() == tradesTab)
navigation.navigateTo(MainView.class, MarketView.class, TradesChartsView.class);
else
navigation.navigateTo(MainView.class, MarketView.class, MarketsStatisticsView.class);
}
@ -82,6 +87,7 @@ public class MarketView extends ActivatableViewAndModel<TabPane, Activatable> {
View view = viewLoader.load(viewClass);
if (view instanceof MarketsChartsView) tab = chartsTab;
else if (view instanceof TradesChartsView) tab = tradesTab;
else if (view instanceof MarketsStatisticsView) tab = statisticsTab;
else throw new IllegalArgumentException("Navigation to " + viewClass + " is not supported");

View file

@ -203,7 +203,7 @@ public class MarketsChartsView extends ActivatableViewAndModel<VBox, MarketsChar
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (offer != null && offer.getPrice() != null) {
setText(formatter.formatFiat(offer.getPrice()));
model.priceFeed.currenciesUpdateFlagProperty().removeListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
}
}
};
@ -214,14 +214,14 @@ public class MarketsChartsView extends ActivatableViewAndModel<VBox, MarketsChar
if (offer != null && !empty) {
if (offer.getPrice() == null) {
this.offer = offer;
model.priceFeed.currenciesUpdateFlagProperty().addListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().addListener(listener);
setText("N/A");
} else {
setText(formatter.formatFiat(offer.getPrice()));
}
} else {
if (listener != null)
model.priceFeed.currenciesUpdateFlagProperty().removeListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
this.offer = null;
setText("");
}
@ -274,7 +274,7 @@ public class MarketsChartsView extends ActivatableViewAndModel<VBox, MarketsChar
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (offer != null && offer.getPrice() != null) {
setText(formatter.formatFiat(offer.getOfferVolume()));
model.priceFeed.currenciesUpdateFlagProperty().removeListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
}
}
};
@ -285,14 +285,14 @@ public class MarketsChartsView extends ActivatableViewAndModel<VBox, MarketsChar
if (offer != null && !empty) {
if (offer.getPrice() == null) {
this.offer = offer;
model.priceFeed.currenciesUpdateFlagProperty().addListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().addListener(listener);
setText("N/A");
} else {
setText(formatter.formatFiat(offer.getOfferVolume()));
}
} else {
if (listener != null)
model.priceFeed.currenciesUpdateFlagProperty().removeListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
this.offer = null;
setText("");
}

View file

@ -19,7 +19,7 @@ package io.bitsquare.gui.main.markets.charts;
import com.google.common.math.LongMath;
import com.google.inject.Inject;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.gui.common.model.ActivatableViewModel;
import io.bitsquare.gui.main.offer.offerbook.OfferBook;
import io.bitsquare.gui.main.offer.offerbook.OfferBookListItem;
@ -51,7 +51,7 @@ class MarketsChartsViewModel extends ActivatableViewModel {
private final OfferBook offerBook;
private final Preferences preferences;
final PriceFeed priceFeed;
final PriceFeedService priceFeedService;
final ObjectProperty<TradeCurrency> tradeCurrency = new SimpleObjectProperty<>();
private final List<XYChart.Data> buyData = new ArrayList<>();
@ -67,10 +67,10 @@ class MarketsChartsViewModel extends ActivatableViewModel {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public MarketsChartsViewModel(OfferBook offerBook, Preferences preferences, PriceFeed priceFeed) {
public MarketsChartsViewModel(OfferBook offerBook, Preferences preferences, PriceFeedService priceFeedService) {
this.offerBook = offerBook;
this.preferences = preferences;
this.priceFeed = priceFeed;
this.priceFeedService = priceFeedService;
Optional<TradeCurrency> tradeCurrencyOptional = CurrencyUtil.getTradeCurrency(preferences.getMarketScreenCurrencyCode());
if (tradeCurrencyOptional.isPresent())
@ -100,7 +100,7 @@ class MarketsChartsViewModel extends ActivatableViewModel {
if (!isAnyPricePresent()) {
offerBook.fillOfferBookListItems();
updateChartData();
priceFeed.currenciesUpdateFlagProperty().removeListener(currenciesUpdatedListener);
priceFeedService.currenciesUpdateFlagProperty().removeListener(currenciesUpdatedListener);
}
}
};
@ -108,17 +108,17 @@ class MarketsChartsViewModel extends ActivatableViewModel {
@Override
protected void activate() {
priceFeed.setType(PriceFeed.Type.LAST);
priceFeedService.setType(PriceFeedService.Type.LAST);
offerBookListItems.addListener(listChangeListener);
offerBook.fillOfferBookListItems();
updateChartData();
if (isAnyPricePresent())
priceFeed.currenciesUpdateFlagProperty().addListener(currenciesUpdatedListener);
priceFeedService.currenciesUpdateFlagProperty().addListener(currenciesUpdatedListener);
if (!preferences.getUseStickyMarketPrice())
priceFeed.setCurrencyCode(tradeCurrency.get().getCode());
priceFeedService.setCurrencyCode(tradeCurrency.get().getCode());
}
@Override
@ -189,7 +189,7 @@ class MarketsChartsViewModel extends ActivatableViewModel {
updateChartData();
if (!preferences.getUseStickyMarketPrice())
priceFeed.setCurrencyCode(tradeCurrency.getCode());
priceFeedService.setCurrencyCode(tradeCurrency.getCode());
preferences.setMarketScreenCurrencyCode(tradeCurrency.getCode());
}

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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/>.
-->
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.*?>
<VBox fx:id="root" fx:controller="io.bitsquare.gui.main.markets.trades.TradesChartsView"
spacing="10.0" fillWidth="true"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
xmlns:fx="http://javafx.com/fxml">
<padding>
<Insets bottom="10.0" left="20.0" top="10.0" right="20"/>
</padding>
</VBox>

View file

@ -0,0 +1,514 @@
/*
* 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.gui.main.markets.trades;
import io.bitsquare.common.UserThread;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.main.markets.trades.charts.price.CandleStickChart;
import io.bitsquare.gui.main.markets.trades.charts.volume.VolumeChart;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.CryptoCurrency;
import io.bitsquare.locale.FiatCurrency;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.trade.statistics.TradeStatistics;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import javafx.util.StringConverter;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.Date;
@FxmlView
public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesChartsViewModel> {
private static final Logger log = LoggerFactory.getLogger(TradesChartsView.class);
private final BSFormatter formatter;
private TableView<TradeStatistics> tableView;
private ComboBox<TradeCurrency> currencyComboBox;
private VolumeChart volumeChart;
private CandleStickChart priceChart;
private NumberAxis priceAxisX, priceAxisY, volumeAxisY, volumeAxisX;
private XYChart.Series<Number, Number> priceSeries;
private XYChart.Series<Number, Number> volumeSeries;
private ChangeListener<Number> priceAxisYWidthListener;
private ChangeListener<Number> volumeAxisYWidthListener;
private double priceAxisYWidth;
private double volumeAxisYWidth;
private final StringProperty priceColumnLabel = new SimpleStringProperty();
private ChangeListener<Toggle> toggleChangeListener;
private ToggleGroup toggleGroup;
private final ListChangeListener<XYChart.Data<Number, Number>> itemsChangeListener;
private Subscription tradeCurrencySubscriber;
private SortedList<TradeStatistics> sortedList;
private Label nrOfTradeStatisticsLabel;
private ListChangeListener<TradeStatistics> tradeStatisticsByCurrencyListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TradesChartsView(TradesChartsViewModel model, BSFormatter formatter) {
super(model);
this.formatter = formatter;
// Need to render on next frame as otherwise there are issues in the chart rendering
itemsChangeListener = c -> UserThread.execute(this::updateChartData);
}
@Override
public void initialize() {
HBox toolBox = getToolBox();
createCharts();
createTable();
nrOfTradeStatisticsLabel = new Label("");
nrOfTradeStatisticsLabel.setId("num-offers");
nrOfTradeStatisticsLabel.setPadding(new Insets(-5, 0, -10, 5));
root.getChildren().addAll(toolBox, priceChart, volumeChart, tableView, nrOfTradeStatisticsLabel);
toggleChangeListener = (observable, oldValue, newValue) -> {
if (newValue != null) {
model.setTickUnit((TradesChartsViewModel.TickUnit) newValue.getUserData());
priceAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
volumeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
}
};
priceAxisYWidthListener = (observable, oldValue, newValue) -> {
priceAxisYWidth = (double) newValue;
layoutChart();
};
volumeAxisYWidthListener = (observable, oldValue, newValue) -> {
volumeAxisYWidth = (double) newValue;
layoutChart();
};
tradeStatisticsByCurrencyListener = c -> nrOfTradeStatisticsLabel.setText("Nr. of trades: " + model.tradeStatisticsByCurrency.size());
}
@Override
protected void activate() {
currencyComboBox.setItems(model.getTradeCurrencies());
currencyComboBox.getSelectionModel().select(model.getTradeCurrency());
currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 25));
currencyComboBox.setOnAction(e -> model.onSetTradeCurrency(currencyComboBox.getSelectionModel().getSelectedItem()));
toggleGroup.getToggles().get(model.tickUnit.ordinal()).setSelected(true);
model.priceItems.addListener(itemsChangeListener);
toggleGroup.selectedToggleProperty().addListener(toggleChangeListener);
priceAxisY.widthProperty().addListener(priceAxisYWidthListener);
volumeAxisY.widthProperty().addListener(volumeAxisYWidthListener);
tradeCurrencySubscriber = EasyBind.subscribe(model.tradeCurrencyProperty,
tradeCurrency -> {
String code = tradeCurrency.getCode();
String tradeCurrencyName = tradeCurrency.getName();
priceSeries.setName(tradeCurrencyName);
final String currencyPair = formatter.getCurrencyPair(code);
priceColumnLabel.set("Price (" + currencyPair + ")");
});
sortedList = new SortedList<>(model.tradeStatisticsByCurrency);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
priceChart.setAnimated(model.preferences.getUseAnimations());
volumeChart.setAnimated(model.preferences.getUseAnimations());
updateChartData();
priceAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
volumeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
model.tradeStatisticsByCurrency.addListener(tradeStatisticsByCurrencyListener);
nrOfTradeStatisticsLabel.setText("Nr. of trades: " + model.tradeStatisticsByCurrency.size());
}
@Override
protected void deactivate() {
model.priceItems.removeListener(itemsChangeListener);
toggleGroup.selectedToggleProperty().removeListener(toggleChangeListener);
priceAxisY.widthProperty().removeListener(priceAxisYWidthListener);
volumeAxisY.widthProperty().removeListener(volumeAxisYWidthListener);
model.tradeStatisticsByCurrency.removeListener(tradeStatisticsByCurrencyListener);
tradeCurrencySubscriber.unsubscribe();
currencyComboBox.setOnAction(null);
priceAxisY.labelProperty().unbind();
priceSeries.getData().clear();
priceChart.getData().clear();
sortedList.comparatorProperty().unbind();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Chart
///////////////////////////////////////////////////////////////////////////////////////////
private void createCharts() {
priceSeries = new XYChart.Series<>();
priceAxisX = new NumberAxis(0, model.maxTicks + 1, 1);
priceAxisX.setTickUnit(1);
priceAxisX.setMinorTickCount(0);
priceAxisX.setForceZeroInRange(false);
priceAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
priceAxisY = new NumberAxis();
priceAxisY.setForceZeroInRange(false);
priceAxisY.setAutoRanging(true);
priceAxisY.labelProperty().bind(priceColumnLabel);
priceAxisY.setTickLabelFormatter(new StringConverter<Number>() {
@Override
public String toString(Number object) {
return formatter.formatFiat(Fiat.valueOf(model.getCurrencyCode(), new Double((double) object).longValue()));
}
@Override
public Number fromString(String string) {
return null;
}
});
priceChart = new CandleStickChart(priceAxisX, priceAxisY);
priceChart.setMinHeight(250);
priceChart.setLegendVisible(false);
priceChart.setData(FXCollections.observableArrayList(priceSeries));
priceChart.setToolTipStringConverter(new StringConverter<Number>() {
@Override
public String toString(Number object) {
return formatter.formatFiatWithCode(Fiat.valueOf(model.getCurrencyCode(), (long) object));
}
@Override
public Number fromString(String string) {
return null;
}
});
volumeSeries = new XYChart.Series<>();
volumeAxisX = new NumberAxis(0, model.maxTicks + 1, 1);
volumeAxisX.setTickUnit(1);
volumeAxisX.setMinorTickCount(0);
volumeAxisX.setForceZeroInRange(false);
volumeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
volumeAxisY = new NumberAxis();
volumeAxisY.setForceZeroInRange(true);
volumeAxisY.setAutoRanging(true);
volumeAxisY.setLabel("Volume (BTC)");
volumeAxisY.setTickLabelFormatter(new StringConverter<Number>() {
@Override
public String toString(Number object) {
return formatter.formatCoin(Coin.valueOf(new Double((double) object).longValue()));
}
@Override
public Number fromString(String string) {
return null;
}
});
volumeChart = new VolumeChart(volumeAxisX, volumeAxisY);
volumeChart.setData(FXCollections.observableArrayList(volumeSeries));
volumeChart.setMinHeight(140);
volumeChart.setLegendVisible(false);
volumeChart.setToolTipStringConverter(new StringConverter<Number>() {
@Override
public String toString(Number object) {
return formatter.formatCoinWithCode(Coin.valueOf(new Double((double) object).longValue()));
}
@Override
public Number fromString(String string) {
return null;
}
});
}
private void updateChartData() {
volumeSeries.getData().setAll(model.volumeItems);
// At price chart we need to set the priceSeries new otherwise the lines are not rendered correctly
// TODO should be fixed in candle chart
priceSeries.getData().clear();
priceSeries = new XYChart.Series<>();
priceSeries.getData().setAll(model.priceItems);
priceChart.getData().clear();
priceChart.setData(FXCollections.observableArrayList(priceSeries));
}
private void layoutChart() {
UserThread.execute(() -> {
if (volumeAxisYWidth > priceAxisYWidth) {
priceChart.setPadding(new Insets(0, 0, 0, volumeAxisYWidth - priceAxisYWidth));
volumeChart.setPadding(new Insets(0, 0, 0, 0));
} else if (volumeAxisYWidth < priceAxisYWidth) {
priceChart.setPadding(new Insets(0, 0, 0, 0));
volumeChart.setPadding(new Insets(0, 0, 0, priceAxisYWidth - volumeAxisYWidth));
}
});
}
@NotNull
private StringConverter<Number> getTimeAxisStringConverter() {
return new StringConverter<Number>() {
@Override
public String toString(Number object) {
long index = new Double((double) object).longValue();
long time = model.getTimeFromTickIndex(index);
if (model.tickUnit.ordinal() <= TradesChartsViewModel.TickUnit.DAY.ordinal())
return index % 4 == 0 ? formatter.formatDate(new Date(time)) : "";
else
return index % 3 == 0 ? formatter.formatTime(new Date(time)) : "";
}
@Override
public Number fromString(String string) {
return null;
}
};
}
///////////////////////////////////////////////////////////////////////////////////////////
// CurrencyComboBox
///////////////////////////////////////////////////////////////////////////////////////////
private HBox getToolBox() {
Label currencyLabel = new Label("Currency:");
currencyLabel.setPadding(new Insets(0, 4, 0, 0));
currencyComboBox = new ComboBox<>();
currencyComboBox.setPromptText("Select currency");
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
@Override
public String toString(TradeCurrency tradeCurrency) {
// http://boschista.deviantart.com/journal/Cool-ASCII-Symbols-214218618
if (tradeCurrency instanceof FiatCurrency)
return "" + tradeCurrency.getNameAndCode();
else if (tradeCurrency instanceof CryptoCurrency)
return "" + tradeCurrency.getNameAndCode();
else
return "-";
}
@Override
public TradeCurrency fromString(String s) {
return null;
}
});
Pane spacer = new Pane();
HBox.setHgrow(spacer, Priority.ALWAYS);
Label label = new Label("Interval:");
label.setPadding(new Insets(0, 4, 0, 0));
toggleGroup = new ToggleGroup();
ToggleButton month = getToggleButton("Month", TradesChartsViewModel.TickUnit.MONTH, toggleGroup, "toggle-left");
ToggleButton week = getToggleButton("Week", TradesChartsViewModel.TickUnit.WEEK, toggleGroup, "toggle-center");
ToggleButton day = getToggleButton("Day", TradesChartsViewModel.TickUnit.DAY, toggleGroup, "toggle-center");
ToggleButton hour = getToggleButton("Hour", TradesChartsViewModel.TickUnit.HOUR, toggleGroup, "toggle-center");
ToggleButton minute10 = getToggleButton("10 Minutes", TradesChartsViewModel.TickUnit.MINUTE_10, toggleGroup, "toggle-center");
ToggleButton minute = getToggleButton("Minute", TradesChartsViewModel.TickUnit.MINUTE, toggleGroup, "toggle-right");
HBox hBox = new HBox();
hBox.setSpacing(0);
hBox.setPadding(new Insets(5, 9, -10, 10));
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.getChildren().addAll(currencyLabel, currencyComboBox, spacer, label, month, week, day, hour, minute10, minute);
return hBox;
}
private ToggleButton getToggleButton(String label, TradesChartsViewModel.TickUnit tickUnit, ToggleGroup toggleGroup, String style) {
ToggleButton toggleButton = new ToggleButton(label);
toggleButton.setPadding(new Insets(0, 5, 0, 5));
toggleButton.setUserData(tickUnit);
toggleButton.setToggleGroup(toggleGroup);
toggleButton.setId(style);
return toggleButton;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table
///////////////////////////////////////////////////////////////////////////////////////////
private void createTable() {
tableView = new TableView<>();
tableView.setMinHeight(120);
// date
TableColumn<TradeStatistics, TradeStatistics> dateColumn = new TableColumn<>("Date/Time");
dateColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
dateColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(formatter.formatDateTime(item.getTradeDate()));
else
setText("");
}
};
}
});
dateColumn.setComparator((o1, o2) -> o1.getTradeDate().compareTo(o2.getTradeDate()));
tableView.getColumns().add(dateColumn);
// amount
TableColumn<TradeStatistics, TradeStatistics> amountColumn = new TableColumn<>("Amount in BTC");
amountColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
amountColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(formatter.formatCoinWithCode(item.getTradeAmount()));
else
setText("");
}
};
}
});
amountColumn.setComparator((o1, o2) -> o1.getTradeAmount().compareTo(o2.getTradeAmount()));
tableView.getColumns().add(amountColumn);
// price
TableColumn<TradeStatistics, TradeStatistics> priceColumn = new TableColumn<>();
priceColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
priceColumn.textProperty().bind(priceColumnLabel);
priceColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(formatter.formatFiat(item.getTradePrice()));
else
setText("");
}
};
}
});
priceColumn.setComparator((o1, o2) -> o1.getTradePrice().compareTo(o2.getTradePrice()));
tableView.getColumns().add(priceColumn);
// volume
TableColumn<TradeStatistics, TradeStatistics> volumeColumn = new TableColumn<>();
volumeColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
volumeColumn.setText("Volume (BTC)");
volumeColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(formatter.formatFiatWithCode(item.getTradeVolume()));
else
setText("");
}
};
}
});
volumeColumn.setComparator((o1, o2) -> {
final Fiat tradeVolume1 = o1.getTradeVolume();
final Fiat tradeVolume2 = o2.getTradeVolume();
return tradeVolume1 != null && tradeVolume2 != null ? tradeVolume1.compareTo(tradeVolume2) : 0;
});
tableView.getColumns().add(volumeColumn);
// direction
TableColumn<TradeStatistics, TradeStatistics> directionColumn = new TableColumn<>("Trade type");
directionColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
directionColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(formatter.getDirection(item.direction));
else
setText("");
}
};
}
});
directionColumn.setComparator((o1, o2) -> o1.direction.compareTo(o2.direction));
tableView.getColumns().add(directionColumn);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Label placeholder = new Label("There is no data available");
placeholder.setWrapText(true);
tableView.setPlaceholder(placeholder);
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
}
}

View file

@ -0,0 +1,270 @@
/*
* 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.gui.main.markets.trades;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.gui.common.model.ActivatableViewModel;
import io.bitsquare.gui.main.markets.trades.charts.CandleData;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.trade.statistics.TradeStatistics;
import io.bitsquare.trade.statistics.TradeStatisticsManager;
import io.bitsquare.user.Preferences;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.SetChangeListener;
import javafx.scene.chart.XYChart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
class TradesChartsViewModel extends ActivatableViewModel {
private static final Logger log = LoggerFactory.getLogger(TradesChartsViewModel.class);
///////////////////////////////////////////////////////////////////////////////////////////
// Enum
///////////////////////////////////////////////////////////////////////////////////////////
public enum TickUnit {
MONTH,
WEEK,
DAY,
HOUR,
MINUTE_10,
MINUTE
}
private final TradeStatisticsManager tradeStatisticsManager;
final Preferences preferences;
private PriceFeedService priceFeedService;
private final SetChangeListener<TradeStatistics> setChangeListener;
final ObjectProperty<TradeCurrency> tradeCurrencyProperty = new SimpleObjectProperty<>();
final ObservableList<TradeStatistics> tradeStatisticsByCurrency = FXCollections.observableArrayList();
ObservableList<XYChart.Data<Number, Number>> priceItems = FXCollections.observableArrayList();
ObservableList<XYChart.Data<Number, Number>> volumeItems = FXCollections.observableArrayList();
TickUnit tickUnit = TickUnit.MONTH;
int maxTicks = 30;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TradesChartsViewModel(TradeStatisticsManager tradeStatisticsManager, Preferences preferences, PriceFeedService priceFeedService) {
this.tradeStatisticsManager = tradeStatisticsManager;
this.preferences = preferences;
this.priceFeedService = priceFeedService;
setChangeListener = change -> updateChartData();
Optional<TradeCurrency> tradeCurrencyOptional = CurrencyUtil.getTradeCurrency(preferences.getTradeStatisticsScreenCurrencyCode());
if (tradeCurrencyOptional.isPresent())
tradeCurrencyProperty.set(tradeCurrencyOptional.get());
else {
tradeCurrencyProperty.set(CurrencyUtil.getDefaultTradeCurrency());
}
tickUnit = TickUnit.values()[preferences.getTradeStatisticsTickUnitIndex()];
}
@VisibleForTesting
TradesChartsViewModel() {
setChangeListener = null;
preferences = null;
tradeStatisticsManager = null;
}
@Override
protected void activate() {
tradeStatisticsManager.getObservableTradeStatisticsSet().addListener(setChangeListener);
updateChartData();
if (!preferences.getUseStickyMarketPrice())
priceFeedService.setCurrencyCode(tradeCurrencyProperty.get().getCode());
}
@Override
protected void deactivate() {
tradeStatisticsManager.getObservableTradeStatisticsSet().removeListener(setChangeListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
public void onSetTradeCurrency(TradeCurrency tradeCurrency) {
this.tradeCurrencyProperty.set(tradeCurrency);
preferences.setTradeStatisticsScreenCurrencyCode(tradeCurrency.getCode());
updateChartData();
if (!preferences.getUseStickyMarketPrice())
priceFeedService.setCurrencyCode(tradeCurrency.getCode());
}
public void setTickUnit(TickUnit tickUnit) {
this.tickUnit = tickUnit;
preferences.setTradeStatisticsTickUnitIndex(tickUnit.ordinal());
updateChartData();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public String getCurrencyCode() {
return tradeCurrencyProperty.get().getCode();
}
public ObservableList<TradeCurrency> getTradeCurrencies() {
return preferences.getTradeCurrenciesAsObservable();
}
public TradeCurrency getTradeCurrency() {
return tradeCurrencyProperty.get();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateChartData() {
tradeStatisticsByCurrency.setAll(tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.currency.equals(getCurrencyCode()))
.collect(Collectors.toList()));
// Get all entries for the defined time interval
Map<Long, Set<TradeStatistics>> itemsPerInterval = new HashMap<>();
tradeStatisticsByCurrency.stream().forEach(e -> {
Set<TradeStatistics> set;
final long time = getTickFromTime(e.tradeDate, tickUnit);
final long now = getTickFromTime(new Date().getTime(), tickUnit);
long index = maxTicks - (now - time);
if (itemsPerInterval.containsKey(index)) {
set = itemsPerInterval.get(index);
} else {
set = new HashSet<>();
itemsPerInterval.put(index, set);
}
set.add(e);
});
// create CandleData for defined time interval
List<CandleData> candleDataList = itemsPerInterval.entrySet().stream()
.map(entry -> getCandleData(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
candleDataList.sort((o1, o2) -> (o1.tick < o2.tick ? -1 : (o1.tick == o2.tick ? 0 : 1)));
priceItems.setAll(candleDataList.stream()
.map(e -> new XYChart.Data<Number, Number>(e.tick, e.open, e))
.collect(Collectors.toList()));
volumeItems.setAll(candleDataList.stream()
.map(e -> new XYChart.Data<Number, Number>(e.tick, e.accumulatedAmount, e))
.collect(Collectors.toList()));
}
@VisibleForTesting
CandleData getCandleData(long tick, Set<TradeStatistics> set) {
long open = 0;
long close = 0;
long high = 0;
long low = 0;
long accumulatedVolume = 0;
long accumulatedAmount = 0;
for (TradeStatistics item : set) {
final long tradePriceAsLong = item.tradePrice;
low = (low != 0) ? Math.min(low, tradePriceAsLong) : tradePriceAsLong;
high = (high != 0) ? Math.max(high, tradePriceAsLong) : tradePriceAsLong;
accumulatedVolume += (item.getTradeVolume() != null) ? item.getTradeVolume().value : 0;
accumulatedAmount += item.tradeAmount;
}
// 100000000 -> Coin.COIN.value;
long averagePrice = Math.round((double) accumulatedVolume * 100000000d / (double) accumulatedAmount);
List<TradeStatistics> list = new ArrayList<>(set);
list.sort((o1, o2) -> (o1.tradeDate < o2.tradeDate ? -1 : (o1.tradeDate == o2.tradeDate ? 0 : 1)));
if (list.size() > 0) {
open = list.get(0).tradePrice;
close = list.get(list.size() - 1).tradePrice;
}
boolean isBullish = close > open;
return new CandleData(tick, open, close, high, low, averagePrice, accumulatedAmount, accumulatedVolume, isBullish);
}
long getTickFromTime(long tradeDateAsTime, TickUnit tickUnit) {
switch (tickUnit) {
case MONTH:
return TimeUnit.MILLISECONDS.toDays(tradeDateAsTime) / 31;
case WEEK:
return TimeUnit.MILLISECONDS.toDays(tradeDateAsTime) / 7;
case DAY:
return TimeUnit.MILLISECONDS.toDays(tradeDateAsTime);
case HOUR:
return TimeUnit.MILLISECONDS.toHours(tradeDateAsTime);
case MINUTE_10:
return TimeUnit.MILLISECONDS.toMinutes(tradeDateAsTime) / 10;
case MINUTE:
return TimeUnit.MILLISECONDS.toMinutes(tradeDateAsTime);
default:
return tradeDateAsTime;
}
}
long getTimeFromTick(long tick, TickUnit tickUnit) {
switch (tickUnit) {
case MONTH:
return TimeUnit.DAYS.toMillis(tick) * 31;
case WEEK:
return TimeUnit.DAYS.toMillis(tick) * 7;
case DAY:
return TimeUnit.DAYS.toMillis(tick);
case HOUR:
return TimeUnit.HOURS.toMillis(tick);
case MINUTE_10:
return TimeUnit.MINUTES.toMillis(tick) * 10;
case MINUTE:
return TimeUnit.MINUTES.toMillis(tick);
default:
return tick;
}
}
long getTimeFromTickIndex(long index) {
long now = getTickFromTime(new Date().getTime(), tickUnit);
long tick = now - (maxTicks - index);
return getTimeFromTick(tick, tickUnit);
}
}

View file

@ -0,0 +1,42 @@
/*
* 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.gui.main.markets.trades.charts;
public class CandleData {
public final long tick; // Is the time tick in the chosen time interval
public final long open;
public final long close;
public final long high;
public final long low;
public final long average;
public final long accumulatedAmount;
public final long accumulatedVolume;
public final boolean isBullish;
public CandleData(long tick, long open, long close, long high, long low, long average, long accumulatedAmount, long accumulatedVolume, boolean isBullish) {
this.tick = tick;
this.open = open;
this.close = close;
this.high = high;
this.low = low;
this.average = average;
this.accumulatedAmount = accumulatedAmount;
this.accumulatedVolume = accumulatedVolume;
this.isBullish = isBullish;
}
}

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2008, 2014, Oracle and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
* This file is available and licensed under the following license:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution.
* - Neither the name of Oracle Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.bitsquare.gui.main.markets.trades.charts.price;
import io.bitsquare.gui.main.markets.trades.charts.CandleData;
import javafx.scene.Group;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Region;
import javafx.scene.shape.Line;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Candle node used for drawing a candle
*/
public class Candle extends Group {
private static final Logger log = LoggerFactory.getLogger(Candle.class);
private String seriesStyleClass;
private String dataStyleClass;
private final TooltipContent tooltipContent;
private final Line highLowLine = new Line();
private final Region bar = new Region();
private boolean openAboveClose = true;
private Tooltip tooltip = new Tooltip();
private double closeOffset;
Candle(String seriesStyleClass, String dataStyleClass, StringConverter<Number> priceStringConverter) {
this.seriesStyleClass = seriesStyleClass;
this.dataStyleClass = dataStyleClass;
setAutoSizeChildren(false);
getChildren().addAll(highLowLine, bar);
getStyleClass().setAll("candlestick-candle", seriesStyleClass, dataStyleClass);
updateStyleClasses();
tooltipContent = new TooltipContent(priceStringConverter);
tooltip.setGraphic(tooltipContent);
Tooltip.install(this, tooltip);
}
public void setSeriesAndDataStyleClasses(String seriesStyleClass, String dataStyleClass) {
this.seriesStyleClass = seriesStyleClass;
this.dataStyleClass = dataStyleClass;
getStyleClass().setAll("candlestick-candle", seriesStyleClass, dataStyleClass);
updateStyleClasses();
}
public void update(double closeOffset, double highOffset, double lowOffset, double candleWidth) {
this.closeOffset = closeOffset;
openAboveClose = closeOffset > 0;
updateStyleClasses();
highLowLine.setStartY(highOffset);
highLowLine.setEndY(lowOffset);
if (openAboveClose) {
bar.resizeRelocate(-candleWidth / 2, 0, candleWidth, Math.max(5, closeOffset));
} else {
bar.resizeRelocate(-candleWidth / 2, closeOffset, candleWidth, Math.max(5, closeOffset * -1));
}
}
public void updateTooltip(CandleData candleData) {
tooltipContent.update(candleData);
}
private void updateStyleClasses() {
String style = openAboveClose ? "open-above-close" : "close-above-open";
if (closeOffset == 0)
style = "empty";
highLowLine.getStyleClass().setAll("candlestick-line", seriesStyleClass, dataStyleClass,
style);
bar.getStyleClass().setAll("candlestick-bar", seriesStyleClass, dataStyleClass,
style);
}
}

View file

@ -0,0 +1,316 @@
/*
* Copyright (c) 2008, 2014, Oracle and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
* This file is available and licensed under the following license:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution.
* - Neither the name of Oracle Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.bitsquare.gui.main.markets.trades.charts.price;
import io.bitsquare.gui.main.markets.trades.charts.CandleData;
import javafx.animation.FadeTransition;
import javafx.event.ActionEvent;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* A candlestick chart is a style of bar-chart used primarily to describe price movements of a security, derivative,
* or currency over time.
* <p>
* The Data Y value is used for the opening price and then the close, high and low values are stored in the Data's
* extra value property using a CandleStickExtraValues object.
*/
public class CandleStickChart extends XYChart<Number, Number> {
private static final Logger log = LoggerFactory.getLogger(CandleStickChart.class);
private StringConverter<Number> priceStringConverter;
// -------------- CONSTRUCTORS ----------------------------------------------
/**
* Construct a new CandleStickChart with the given axis.
*
* @param xAxis The x axis to use
* @param yAxis The y axis to use
*/
public CandleStickChart(Axis<Number> xAxis, Axis<Number> yAxis) {
super(xAxis, yAxis);
}
// -------------- METHODS ------------------------------------------------------------------------------------------
public final void setToolTipStringConverter(StringConverter<Number> priceStringConverter) {
this.priceStringConverter = priceStringConverter;
}
/**
* Called to update and layout the content for the plot
*/
@Override
protected void layoutPlotChildren() {
// we have nothing to layout if no data is present
if (getData() == null) {
return;
}
// update candle positions
for (int seriesIndex = 0; seriesIndex < getData().size(); seriesIndex++) {
XYChart.Series<Number, Number> series = getData().get(seriesIndex);
Iterator<XYChart.Data<Number, Number>> iter = getDisplayedDataIterator(series);
Path seriesPath = null;
if (series.getNode() instanceof Path) {
seriesPath = (Path) series.getNode();
seriesPath.getElements().clear();
}
while (iter.hasNext()) {
XYChart.Data<Number, Number> item = iter.next();
double x = getXAxis().getDisplayPosition(getCurrentDisplayedXValue(item));
double y = getYAxis().getDisplayPosition(getCurrentDisplayedYValue(item));
Node itemNode = item.getNode();
CandleData candleData = (CandleData) item.getExtraValue();
if (itemNode instanceof Candle && candleData != null) {
Candle candle = (Candle) itemNode;
double close = getYAxis().getDisplayPosition(candleData.close);
double high = getYAxis().getDisplayPosition(candleData.high);
double low = getYAxis().getDisplayPosition(candleData.low);
// calculate candle width
double candleWidth = -1;
if (getXAxis() instanceof NumberAxis) {
NumberAxis xa = (NumberAxis) getXAxis();
candleWidth = xa.getDisplayPosition(xa.getTickUnit()) * 0.90; // use 90% width between ticks
}
// update candle
candle.update(close - y, high - y, low - y, candleWidth);
candle.updateTooltip(candleData);
// position the candle
candle.setLayoutX(x);
candle.setLayoutY(y);
}
if (seriesPath != null && candleData != null) {
final double displayPosition = getYAxis().getDisplayPosition(candleData.average);
if (seriesPath.getElements().isEmpty())
seriesPath.getElements().add(new MoveTo(x, displayPosition));
else
seriesPath.getElements().add(new LineTo(x, displayPosition));
}
}
}
}
@Override
protected void dataItemChanged(XYChart.Data<Number, Number> item) {
}
@Override
protected void dataItemAdded(XYChart.Series<Number, Number> series, int itemIndex, XYChart.Data<Number, Number> item) {
Node candle = createCandle(getData().indexOf(series), item, itemIndex);
if (getPlotChildren().contains(candle))
getPlotChildren().remove(candle);
if (shouldAnimate()) {
candle.setOpacity(0);
getPlotChildren().add(candle);
// fade in new candle
FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
ft.setToValue(1);
ft.play();
} else {
getPlotChildren().add(candle);
}
// always draw average line on top
if (series.getNode() instanceof Path) {
Path seriesPath = (Path) series.getNode();
seriesPath.toFront();
}
}
@Override
protected void dataItemRemoved(XYChart.Data<Number, Number> item, XYChart.Series<Number, Number> series) {
if (series.getNode() instanceof Path) {
Path seriesPath = (Path) series.getNode();
seriesPath.getElements().clear();
}
final Node node = item.getNode();
if (shouldAnimate()) {
// fade out old candle
FadeTransition ft = new FadeTransition(Duration.millis(500), node);
ft.setToValue(0);
ft.setOnFinished((ActionEvent actionEvent) -> {
getPlotChildren().remove(node);
});
ft.play();
} else {
getPlotChildren().remove(node);
}
}
@Override
protected void seriesAdded(XYChart.Series<Number, Number> series, int seriesIndex) {
// handle any data already in series
for (int j = 0; j < series.getData().size(); j++) {
XYChart.Data item = series.getData().get(j);
Node candle = createCandle(seriesIndex, item, j);
if (!getPlotChildren().contains(candle)) {
getPlotChildren().add(candle);
if (shouldAnimate()) {
candle.setOpacity(0);
FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
ft.setToValue(1);
ft.play();
}
}
}
Path seriesPath = new Path();
seriesPath.getStyleClass().setAll("candlestick-average-line", "series" + seriesIndex);
series.setNode(seriesPath);
if (!getPlotChildren().contains(seriesPath)) {
getPlotChildren().add(seriesPath);
if (shouldAnimate()) {
seriesPath.setOpacity(0);
FadeTransition ft = new FadeTransition(Duration.millis(500), seriesPath);
ft.setToValue(1);
ft.play();
}
}
}
@Override
protected void seriesRemoved(XYChart.Series<Number, Number> series) {
// remove all candle nodes
for (XYChart.Data<Number, Number> d : series.getData()) {
final Node candle = d.getNode();
if (shouldAnimate()) {
FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
ft.setToValue(0);
ft.setOnFinished((ActionEvent actionEvent) -> {
getPlotChildren().remove(candle);
});
ft.play();
} else {
getPlotChildren().remove(candle);
}
}
if (series.getNode() instanceof Path) {
Path seriesPath = (Path) series.getNode();
if (shouldAnimate()) {
FadeTransition ft = new FadeTransition(Duration.millis(500), seriesPath);
ft.setToValue(0);
ft.setOnFinished((ActionEvent actionEvent) -> {
getPlotChildren().remove(seriesPath);
seriesPath.getElements().clear();
});
ft.play();
} else {
getPlotChildren().remove(seriesPath);
seriesPath.getElements().clear();
}
}
}
/**
* Create a new Candle node to represent a single data item
*
* @param seriesIndex The index of the series the data item is in
* @param item The data item to create node for
* @param itemIndex The index of the data item in the series
* @return New candle node to represent the give data item
*/
private Node createCandle(int seriesIndex, final XYChart.Data item, int itemIndex) {
Node candle = item.getNode();
// check if candle has already been created
if (candle instanceof Candle) {
((Candle) candle).setSeriesAndDataStyleClasses("series" + seriesIndex, "data" + itemIndex);
} else {
candle = new Candle("series" + seriesIndex, "data" + itemIndex, priceStringConverter);
item.setNode(candle);
}
return candle;
}
/**
* This is called when the range has been invalidated and we need to update it. If the axis are auto
* ranging then we compile a list of all data that the given axis has to plot and call invalidateRange() on the
* axis passing it that data.
*/
@Override
protected void updateAxisRange() {
// For candle stick chart we need to override this method as we need to let the axis know that they need to be able
// to cover the whole area occupied by the high to low range not just its center data value
final Axis<Number> xa = getXAxis();
final Axis<Number> ya = getYAxis();
List<Number> xData = null;
List<Number> yData = null;
if (xa.isAutoRanging()) {
xData = new ArrayList<>();
}
if (ya.isAutoRanging()) {
yData = new ArrayList<>();
}
if (xData != null || yData != null) {
for (XYChart.Series<Number, Number> series : getData()) {
for (XYChart.Data<Number, Number> data : series.getData()) {
if (xData != null) {
xData.add(data.getXValue());
}
if (yData != null) {
if (data.getExtraValue() instanceof CandleData) {
CandleData candleData = (CandleData) data.getExtraValue();
yData.add(candleData.high);
yData.add(candleData.low);
} else {
yData.add(data.getYValue());
}
}
}
}
if (xData != null) {
xa.invalidateRange(xData);
}
if (yData != null) {
ya.invalidateRange(yData);
}
}
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2008, 2014, Oracle and/or its affiliates.
* All rights reserved. Use is subject to license terms.
*
* This file is available and licensed under the following license:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the distribution.
* - Neither the name of Oracle Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package io.bitsquare.gui.main.markets.trades.charts.price;
import io.bitsquare.gui.main.markets.trades.charts.CandleData;
import io.bitsquare.gui.util.Layout;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.util.StringConverter;
/**
* The content for Candle tool tips
*/
public class TooltipContent extends GridPane {
private final StringConverter<Number> priceStringConverter;
private final Label openValue = new Label();
private final Label closeValue = new Label();
private final Label highValue = new Label();
private final Label lowValue = new Label();
private final Label averageValue = new Label();
TooltipContent(StringConverter<Number> priceStringConverter) {
this.priceStringConverter = priceStringConverter;
setHgap(Layout.GRID_GAP);
setVgap(2);
Label open = new Label("Open:");
Label close = new Label("Close:");
Label high = new Label("High:");
Label low = new Label("Low:");
Label average = new Label("Average:");
/* open.getStyleClass().add("candlestick-tooltip-label");
close.getStyleClass().add("candlestick-tooltip-label");
high.getStyleClass().add("candlestick-tooltip-label");
low.getStyleClass().add("candlestick-tooltip-label");*/
setConstraints(open, 0, 0);
setConstraints(openValue, 1, 0);
setConstraints(close, 0, 1);
setConstraints(closeValue, 1, 1);
setConstraints(high, 0, 2);
setConstraints(highValue, 1, 2);
setConstraints(low, 0, 3);
setConstraints(lowValue, 1, 3);
setConstraints(average, 0, 4);
setConstraints(averageValue, 1, 4);
getChildren().addAll(open, openValue, close, closeValue, high, highValue, low, lowValue, average, averageValue);
}
public void update(CandleData candleData) {
openValue.setText(priceStringConverter.toString(candleData.open));
closeValue.setText(priceStringConverter.toString(candleData.close));
highValue.setText(priceStringConverter.toString(candleData.high));
lowValue.setText(priceStringConverter.toString(candleData.low));
averageValue.setText(priceStringConverter.toString(candleData.average));
}
}

View file

@ -0,0 +1,62 @@
/*
* 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.gui.main.markets.trades.charts.volume;
import javafx.scene.Group;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Region;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VolumeBar extends Group {
private static final Logger log = LoggerFactory.getLogger(VolumeBar.class);
private String seriesStyleClass;
private String dataStyleClass;
private final StringConverter<Number> volumeStringConverter;
private final Region bar = new Region();
private final Tooltip tooltip;
VolumeBar(String seriesStyleClass, String dataStyleClass, StringConverter<Number> volumeStringConverter) {
this.seriesStyleClass = seriesStyleClass;
this.dataStyleClass = dataStyleClass;
this.volumeStringConverter = volumeStringConverter;
setAutoSizeChildren(false);
getChildren().add(bar);
updateStyleClasses();
tooltip = new Tooltip();
Tooltip.install(this, tooltip);
}
public void setSeriesAndDataStyleClasses(String seriesStyleClass, String dataStyleClass) {
this.seriesStyleClass = seriesStyleClass;
this.dataStyleClass = dataStyleClass;
updateStyleClasses();
}
public void update(double height, double candleWidth, double accumulatedAmount) {
bar.resizeRelocate(-candleWidth / 2, 0, candleWidth, height);
tooltip.setText("Volume: " + volumeStringConverter.toString(accumulatedAmount));
}
private void updateStyleClasses() {
bar.getStyleClass().setAll("volume-bar", seriesStyleClass, dataStyleClass, "bg");
}
}

View file

@ -0,0 +1,190 @@
/*
* 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.gui.main.markets.trades.charts.volume;
import io.bitsquare.gui.main.markets.trades.charts.CandleData;
import io.bitsquare.gui.main.markets.trades.charts.price.CandleStickChart;
import javafx.animation.FadeTransition;
import javafx.event.ActionEvent;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class VolumeChart extends XYChart<Number, Number> {
private static final Logger log = LoggerFactory.getLogger(CandleStickChart.class);
private StringConverter<Number> toolTipStringConverter;
public VolumeChart(Axis<Number> xAxis, Axis<Number> yAxis) {
super(xAxis, yAxis);
}
public final void setToolTipStringConverter(StringConverter<Number> toolTipStringConverter) {
this.toolTipStringConverter = toolTipStringConverter;
}
@Override
protected void layoutPlotChildren() {
if (getData() == null) {
return;
}
for (int seriesIndex = 0; seriesIndex < getData().size(); seriesIndex++) {
XYChart.Series<Number, Number> series = getData().get(seriesIndex);
Iterator<XYChart.Data<Number, Number>> iter = getDisplayedDataIterator(series);
while (iter.hasNext()) {
XYChart.Data<Number, Number> item = iter.next();
double x = getXAxis().getDisplayPosition(getCurrentDisplayedXValue(item));
double y = getYAxis().getDisplayPosition(getCurrentDisplayedYValue(item));
Node itemNode = item.getNode();
CandleData candleData = (CandleData) item.getExtraValue();
if (itemNode instanceof VolumeBar && candleData != null) {
VolumeBar volumeBar = (VolumeBar) itemNode;
double candleWidth = -1;
if (getXAxis() instanceof NumberAxis) {
NumberAxis xa = (NumberAxis) getXAxis();
candleWidth = xa.getDisplayPosition(xa.getTickUnit()) * 0.90; // use 90% width between ticks
}
// 97 is visible chart data height if chart height is 140.
// So we subtract 43 form the height to get the height for the bar to the bottom.
// Did not find a way how to request the chart data height
final double height = getHeight() - 43;
double upperYPos = Math.min(height - 5, y); // We want min 5px height to allow tooltips
volumeBar.update(height - upperYPos, candleWidth, candleData.accumulatedAmount);
volumeBar.setLayoutX(x);
volumeBar.setLayoutY(upperYPos);
}
}
}
}
@Override
protected void dataItemChanged(XYChart.Data<Number, Number> item) {
}
@Override
protected void dataItemAdded(XYChart.Series<Number, Number> series, int itemIndex, XYChart.Data<Number, Number> item) {
Node volumeBar = createCandle(getData().indexOf(series), item, itemIndex);
if (getPlotChildren().contains(volumeBar))
getPlotChildren().remove(volumeBar);
if (shouldAnimate()) {
volumeBar.setOpacity(0);
getPlotChildren().add(volumeBar);
FadeTransition ft = new FadeTransition(Duration.millis(500), volumeBar);
ft.setToValue(1);
ft.play();
} else {
getPlotChildren().add(volumeBar);
}
}
@Override
protected void dataItemRemoved(XYChart.Data<Number, Number> item, XYChart.Series<Number, Number> series) {
final Node node = item.getNode();
if (shouldAnimate()) {
FadeTransition ft = new FadeTransition(Duration.millis(500), node);
ft.setToValue(0);
ft.setOnFinished((ActionEvent actionEvent) -> getPlotChildren().remove(node));
ft.play();
} else {
getPlotChildren().remove(node);
}
}
@Override
protected void seriesAdded(XYChart.Series<Number, Number> series, int seriesIndex) {
for (int j = 0; j < series.getData().size(); j++) {
XYChart.Data item = series.getData().get(j);
Node volumeBar = createCandle(seriesIndex, item, j);
if (shouldAnimate()) {
volumeBar.setOpacity(0);
getPlotChildren().add(volumeBar);
FadeTransition ft = new FadeTransition(Duration.millis(500), volumeBar);
ft.setToValue(1);
ft.play();
} else {
getPlotChildren().add(volumeBar);
}
}
}
@Override
protected void seriesRemoved(XYChart.Series<Number, Number> series) {
for (XYChart.Data<Number, Number> d : series.getData()) {
final Node volumeBar = d.getNode();
if (shouldAnimate()) {
FadeTransition ft = new FadeTransition(Duration.millis(500), volumeBar);
ft.setToValue(0);
ft.setOnFinished((ActionEvent actionEvent) -> getPlotChildren().remove(volumeBar));
ft.play();
} else {
getPlotChildren().remove(volumeBar);
}
}
}
private Node createCandle(int seriesIndex, final XYChart.Data item, int itemIndex) {
Node volumeBar = item.getNode();
if (volumeBar instanceof VolumeBar) {
((VolumeBar) volumeBar).setSeriesAndDataStyleClasses("series" + seriesIndex, "data" + itemIndex);
} else {
volumeBar = new VolumeBar("series" + seriesIndex, "data" + itemIndex, toolTipStringConverter);
item.setNode(volumeBar);
}
return volumeBar;
}
@Override
protected void updateAxisRange() {
final Axis<Number> xa = getXAxis();
final Axis<Number> ya = getYAxis();
List<Number> xData = null;
List<Number> yData = null;
if (xa.isAutoRanging()) {
xData = new ArrayList<>();
}
if (ya.isAutoRanging())
yData = new ArrayList<>();
if (xData != null || yData != null) {
for (XYChart.Series<Number, Number> series : getData()) {
for (XYChart.Data<Number, Number> data : series.getData()) {
if (xData != null) {
xData.add(data.getXValue());
}
if (yData != null)
yData.add(data.getYValue());
}
}
if (xData != null) {
xa.invalidateRange(xData);
}
if (yData != null) {
ya.invalidateRange(yData);
}
}
}
}

View file

@ -17,7 +17,7 @@
package io.bitsquare.gui.main.offer;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.common.view.ViewLoader;
@ -29,8 +29,8 @@ import javax.inject.Inject;
public class BuyOfferView extends OfferView {
@Inject
public BuyOfferView(ViewLoader viewLoader, Navigation navigation, PriceFeed priceFeed, Preferences preferences) {
super(viewLoader, navigation, priceFeed, preferences);
public BuyOfferView(ViewLoader viewLoader, Navigation navigation, PriceFeedService priceFeedService, Preferences preferences) {
super(viewLoader, navigation, priceFeedService, preferences);
}
@Override

View file

@ -17,7 +17,7 @@
package io.bitsquare.gui.main.offer;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.UserThread;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.ActivatableView;
@ -54,7 +54,7 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
private final ViewLoader viewLoader;
private final Navigation navigation;
private final PriceFeed priceFeed;
private final PriceFeedService priceFeedService;
private Preferences preferences;
private final Offer.Direction direction;
private Tab takeOfferTab, createOfferTab, offerBookTab;
@ -63,10 +63,10 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
private ChangeListener<Tab> tabChangeListener;
private ListChangeListener<Tab> tabListChangeListener;
protected OfferView(ViewLoader viewLoader, Navigation navigation, PriceFeed priceFeed, Preferences preferences) {
protected OfferView(ViewLoader viewLoader, Navigation navigation, PriceFeedService priceFeedService, Preferences preferences) {
this.viewLoader = viewLoader;
this.navigation = navigation;
this.priceFeed = priceFeed;
this.priceFeedService = priceFeedService;
this.preferences = preferences;
this.direction = (this instanceof BuyOfferView) ? Offer.Direction.BUY : Offer.Direction.SELL;
}
@ -139,7 +139,7 @@ public abstract class OfferView extends ActivatableView<TabPane, Void> {
View view;
boolean isBuy = direction == Offer.Direction.BUY;
priceFeed.setType(isBuy ? PriceFeed.Type.ASK : PriceFeed.Type.BID);
priceFeedService.setType(isBuy ? PriceFeedService.Type.ASK : PriceFeedService.Type.BID);
if (viewClass == OfferBookView.class && offerBookView == null) {
view = viewLoader.load(viewClass);

View file

@ -17,7 +17,7 @@
package io.bitsquare.gui.main.offer;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.common.view.ViewLoader;
@ -29,8 +29,8 @@ import javax.inject.Inject;
public class SellOfferView extends OfferView {
@Inject
public SellOfferView(ViewLoader viewLoader, Navigation navigation, PriceFeed priceFeed, Preferences preferences) {
super(viewLoader, navigation, priceFeed, preferences);
public SellOfferView(ViewLoader viewLoader, Navigation navigation, PriceFeedService priceFeedService, Preferences preferences) {
super(viewLoader, navigation, priceFeedService, preferences);
}
@Override

View file

@ -26,7 +26,7 @@ import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.blockchain.BlockchainService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ActivatableDataModel;
@ -70,7 +70,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
private final User user;
private final KeyRing keyRing;
private final P2PService p2PService;
private final PriceFeed priceFeed;
private final PriceFeedService priceFeedService;
final String shortOfferId;
private Navigation navigation;
private final BlockchainService blockchainService;
@ -122,7 +122,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
@Inject
CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, TradeWalletService tradeWalletService,
Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, PriceFeed priceFeed,
Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, PriceFeedService priceFeedService,
Navigation navigation, BlockchainService blockchainService, BSFormatter formatter) {
this.openOfferManager = openOfferManager;
this.walletService = walletService;
@ -131,7 +131,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
this.user = user;
this.keyRing = keyRing;
this.p2PService = p2PService;
this.priceFeed = priceFeed;
this.priceFeedService = priceFeedService;
this.navigation = navigation;
this.blockchainService = blockchainService;
this.formatter = formatter;
@ -187,7 +187,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
paymentAccounts.setAll(user.getPaymentAccounts());
if (!preferences.getUseStickyMarketPrice() && isTabSelected)
priceFeed.setCurrencyCode(tradeCurrencyCode.get());
priceFeedService.setCurrencyCode(tradeCurrencyCode.get());
updateBalance();
}
@ -243,7 +243,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
tradeCurrencyCode.set(this.tradeCurrency.getCode());
if (!preferences.getUseStickyMarketPrice())
priceFeed.setCurrencyCode(tradeCurrencyCode.get());
priceFeedService.setCurrencyCode(tradeCurrencyCode.get());
calculateVolume();
calculateTotalToPay();
@ -253,7 +253,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
void onTabSelected(boolean isSelected) {
this.isTabSelected = isSelected;
if (!preferences.getUseStickyMarketPrice() && isTabSelected)
priceFeed.setCurrencyCode(tradeCurrencyCode.get());
priceFeedService.setCurrencyCode(tradeCurrencyCode.get());
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -306,7 +306,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
acceptedCountryCodes,
bankId,
acceptedBanks,
priceFeed);
priceFeedService);
}
void onPlaceOffer(Offer offer, TransactionResultHandler resultHandler) {
@ -328,7 +328,7 @@ class CreateOfferDataModel extends ActivatableDataModel {
paymentAccount.setSelectedTradeCurrency(tradeCurrency);
if (!preferences.getUseStickyMarketPrice())
priceFeed.setCurrencyCode(code);
priceFeedService.setCurrencyCode(code);
Optional<TradeCurrency> tradeCurrencyOptional = preferences.getTradeCurrenciesAsObservable().stream().filter(e -> e.getCode().equals(code)).findAny();
if (!tradeCurrencyOptional.isPresent()) {

View file

@ -208,8 +208,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
if (DevFlags.STRESS_TEST_MODE)
UserThread.runAfter(this::onShowPayFundsScreen, 200, TimeUnit.MILLISECONDS);
// if (DevFlags.STRESS_TEST_MODE)
// UserThread.runAfter(this::onShowPayFundsScreen, 200, TimeUnit.MILLISECONDS);
}
}

View file

@ -19,7 +19,7 @@ package io.bitsquare.gui.main.offer.createoffer;
import io.bitsquare.app.DevFlags;
import io.bitsquare.btc.pricefeed.MarketPrice;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Utilities;
@ -59,7 +59,7 @@ import static javafx.beans.binding.Bindings.createStringBinding;
class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel> implements ViewModel {
private final BtcValidator btcValidator;
private final P2PService p2PService;
private PriceFeed priceFeed;
private PriceFeedService priceFeedService;
private Preferences preferences;
private Navigation navigation;
final BSFormatter formatter;
@ -127,7 +127,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
private ChangeListener<String> errorMessageListener;
private Offer offer;
private Timer timeoutTimer;
private PriceFeed.Type priceFeedType;
private PriceFeedService.Type priceFeedType;
private boolean inputIsMarketBasedPrice;
private ChangeListener<Boolean> useMarketBasedPriceListener;
private ChangeListener<String> currencyCodeListener;
@ -139,14 +139,14 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
@Inject
public CreateOfferViewModel(CreateOfferDataModel dataModel, FiatValidator fiatValidator, BtcValidator btcValidator,
P2PService p2PService, PriceFeed priceFeed, Preferences preferences, Navigation navigation,
P2PService p2PService, PriceFeedService priceFeedService, Preferences preferences, Navigation navigation,
BSFormatter formatter) {
super(dataModel);
this.fiatValidator = fiatValidator;
this.btcValidator = btcValidator;
this.p2PService = p2PService;
this.priceFeed = priceFeed;
this.priceFeedService = priceFeedService;
this.preferences = preferences;
this.navigation = navigation;
this.formatter = formatter;
@ -257,7 +257,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
dataModel.calculateTotalToPay();
if (!inputIsMarketBasedPrice) {
MarketPrice marketPrice = priceFeed.getMarketPrice(dataModel.tradeCurrencyCode.get());
MarketPrice marketPrice = priceFeedService.getMarketPrice(dataModel.tradeCurrencyCode.get());
if (marketPrice != null) {
double marketPriceAsDouble = formatter.roundDouble(marketPrice.getPrice(priceFeedType), 2);
try {
@ -289,7 +289,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
new Popup().warning("You cannot set a percentage of 100% or larger. Please enter a percentage number like \"5.4\" for 5.4%")
.show();
} else {
MarketPrice marketPrice = priceFeed.getMarketPrice(dataModel.tradeCurrencyCode.get());
MarketPrice marketPrice = priceFeedService.getMarketPrice(dataModel.tradeCurrencyCode.get());
if (marketPrice != null) {
marketPriceMargin = formatter.roundDouble(marketPriceMargin, 4);
dataModel.setMarketPriceMargin(marketPriceMargin);
@ -337,9 +337,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
priceAsFiatListener = (ov, oldValue, newValue) -> price.set(formatter.formatFiat(newValue));
volumeAsFiatListener = (ov, oldValue, newValue) -> volume.set(formatter.formatFiat(newValue));
isWalletFundedListener = (ov, oldValue, newValue) -> {
updateButtonDisableState();
};
isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState();
/* feeFromFundingTxListener = (ov, oldValue, newValue) -> {
updateButtonDisableState();
};*/
@ -350,20 +348,12 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
//TODO remove after AUGUST, 30
private void applyCurrencyCode(String newValue) {
String key = "ETH-ETHC-Warning";
String key = "ETH-ETC-Warning";
if (preferences.showAgain(key) && new Date().before(new Date(2016 - 1900, Calendar.AUGUST, 30))) {
if (newValue.equals("ETH")) {
new Popup().information("The EHT/ETHC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://www.ethereum.org/"))
.actionButtonText("Open Ethereum web page")
.dontShowAgainId(key, preferences)
.show();
} else if (newValue.equals("ETHC")) {
new Popup().information("The EHT/ETHC fork situation carries considerable risks.\n" +
if (newValue.equals("ETC")) {
new Popup().information("The EHT/ETC fork situation carries considerable risks.\n" +
"Be sure you fully understand the situation and check out the information on the \"Ethereum Classic\" and \"Ethereum\" project web pages.\n\n" +
"Please note, that the price is denominated as ETHC/BTC not BTC/ETHC!")
"Please note, that the price is denominated as ETC/BTC not BTC/ETC!")
.closeButtonText("I understand")
.onAction(() -> Utilities.openWebPage("https://ethereumclassic.github.io/"))
.actionButtonText("Open Ethereum Classic web page")
@ -430,7 +420,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
if (dataModel.paymentAccount != null)
btcValidator.setMaxTradeLimitInBitcoin(dataModel.paymentAccount.getPaymentMethod().getMaxTradeLimit());
priceFeedType = direction == Offer.Direction.BUY ? PriceFeed.Type.ASK : PriceFeed.Type.BID;
priceFeedType = direction == Offer.Direction.BUY ? PriceFeedService.Type.ASK : PriceFeedService.Type.BID;
return result;
}
@ -623,7 +613,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
///////////////////////////////////////////////////////////////////////////////////////////
public boolean isPriceInRange() {
MarketPrice marketPrice = priceFeed.getMarketPrice(getTradeCurrency().getCode());
MarketPrice marketPrice = priceFeedService.getMarketPrice(getTradeCurrency().getCode());
if (marketPrice != null) {
double marketPriceAsDouble = marketPrice.getPrice(priceFeedType);
Fiat priceAsFiat = dataModel.priceAsFiat.get();

View file

@ -191,7 +191,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
paymentMethodColumn.setComparator((o1, o2) -> o1.getOffer().getPaymentMethod().compareTo(o2.getOffer().getPaymentMethod()));
avatarColumn.setComparator((o1, o2) -> o1.getOffer().getOwnerNodeAddress().hostName.compareTo(o2.getOffer().getOwnerNodeAddress().hostName));
nrOfOffersLabel = new Label("Nr. of offers: -");
nrOfOffersLabel = new Label("");
nrOfOffersLabel.setId("num-offers");
GridPane.setHalignment(nrOfOffersLabel, HPos.LEFT);
GridPane.setVgrow(nrOfOffersLabel, Priority.NEVER);
@ -209,6 +209,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
GridPane.setHalignment(createOfferButton, HPos.RIGHT);
GridPane.setVgrow(createOfferButton, Priority.NEVER);
GridPane.setValignment(createOfferButton, VPos.TOP);
offerListListener = c -> nrOfOffersLabel.setText("Nr. of offers: " + model.getOfferList().size());
}
@Override
@ -254,7 +255,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
tableView.setItems(model.getOfferList());
priceColumn.setSortType((model.getDirection() == Offer.Direction.BUY) ? TableColumn.SortType.ASCENDING : TableColumn.SortType.DESCENDING);
offerListListener = c -> nrOfOffersLabel.setText("Nr. of offers: " + model.getOfferList().size());
model.getOfferList().addListener(offerListListener);
nrOfOffersLabel.setText("Nr. of offers: " + model.getOfferList().size());
}
@ -478,7 +478,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (offerBookListItem != null && offerBookListItem.getOffer().getPrice() != null) {
setText(model.getPrice(offerBookListItem));
model.priceFeed.currenciesUpdateFlagProperty().removeListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
}
}
};
@ -490,14 +490,14 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
if (item != null && !empty) {
if (item.getOffer().getPrice() == null) {
this.offerBookListItem = item;
model.priceFeed.currenciesUpdateFlagProperty().addListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().addListener(listener);
setText("N/A");
} else {
setText(model.getPrice(item));
}
} else {
if (listener != null)
model.priceFeed.currenciesUpdateFlagProperty().removeListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
this.offerBookListItem = null;
setText("");
}
@ -528,7 +528,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (offerBookListItem != null && offerBookListItem.getOffer().getOfferVolume() != null) {
setText(model.getVolume(offerBookListItem));
model.priceFeed.currenciesUpdateFlagProperty().removeListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
}
}
};
@ -539,14 +539,14 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
if (item != null && !empty) {
if (item.getOffer().getPrice() == null) {
this.offerBookListItem = item;
model.priceFeed.currenciesUpdateFlagProperty().addListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().addListener(listener);
setText("N/A");
} else {
setText(model.getVolume(item));
}
} else {
if (listener != null)
model.priceFeed.currenciesUpdateFlagProperty().removeListener(listener);
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
this.offerBookListItem = null;
setText("");
}

View file

@ -20,7 +20,7 @@ package io.bitsquare.gui.main.offer.offerbook;
import com.google.common.base.Joiner;
import com.google.inject.Inject;
import io.bitsquare.app.Version;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.common.handlers.ErrorMessageHandler;
import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.filter.FilterManager;
@ -33,7 +33,8 @@ import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.*;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.payment.*;
import io.bitsquare.payment.PaymentAccountUtil;
import io.bitsquare.payment.PaymentMethod;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.closed.ClosedTradableManager;
import io.bitsquare.trade.offer.Offer;
@ -53,13 +54,9 @@ import org.bitcoinj.utils.Fiat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
class OfferBookViewModel extends ActivatableViewModel {
protected final static Logger log = LoggerFactory.getLogger(OfferBookViewModel.class);
@ -71,7 +68,7 @@ class OfferBookViewModel extends ActivatableViewModel {
private final OfferBook offerBook;
final Preferences preferences;
private final P2PService p2PService;
final PriceFeed priceFeed;
final PriceFeedService priceFeedService;
private ClosedTradableManager closedTradableManager;
private FilterManager filterManager;
private Navigation navigation;
@ -105,7 +102,7 @@ class OfferBookViewModel extends ActivatableViewModel {
@Inject
public OfferBookViewModel(User user, OpenOfferManager openOfferManager, OfferBook offerBook,
Preferences preferences, P2PService p2PService, PriceFeed priceFeed,
Preferences preferences, P2PService p2PService, PriceFeedService priceFeedService,
ClosedTradableManager closedTradableManager, FilterManager filterManager,
Navigation navigation, BSFormatter formatter) {
super();
@ -115,7 +112,7 @@ class OfferBookViewModel extends ActivatableViewModel {
this.offerBook = offerBook;
this.preferences = preferences;
this.p2PService = p2PService;
this.priceFeed = priceFeed;
this.priceFeedService = priceFeedService;
this.closedTradableManager = closedTradableManager;
this.filterManager = filterManager;
this.navigation = navigation;
@ -157,9 +154,9 @@ class OfferBookViewModel extends ActivatableViewModel {
private void setMarketPriceFeedCurrency() {
if (!preferences.getUseStickyMarketPrice() && isTabSelected) {
if (showAllTradeCurrenciesProperty.get())
priceFeed.setCurrencyCode(CurrencyUtil.getDefaultTradeCurrency().getCode());
priceFeedService.setCurrencyCode(CurrencyUtil.getDefaultTradeCurrency().getCode());
else
priceFeed.setCurrencyCode(tradeCurrencyCode.get());
priceFeedService.setCurrencyCode(tradeCurrencyCode.get());
}
}
@ -358,70 +355,7 @@ class OfferBookViewModel extends ActivatableViewModel {
}
boolean isAnyPaymentAccountValidForOffer(Offer offer) {
return isAnyPaymentAccountValidForOffer(offer, user.getPaymentAccounts());
}
static boolean isAnyPaymentAccountValidForOffer(Offer offer, Collection<PaymentAccount> paymentAccounts) {
for (PaymentAccount paymentAccount : paymentAccounts) {
if (isPaymentAccountValidForOffer(offer, paymentAccount))
return true;
}
return false;
}
//TODO not tested with all combinations yet....
static boolean isPaymentAccountValidForOffer(Offer offer, PaymentAccount paymentAccount) {
// check if we have a matching currency
Set<String> paymentAccountCurrencyCodes = paymentAccount.getTradeCurrencies().stream().map(TradeCurrency::getCode).collect(Collectors.toSet());
boolean matchesCurrencyCode = paymentAccountCurrencyCodes.contains(offer.getCurrencyCode());
if (!matchesCurrencyCode)
return false;
// check if we have a matching payment method or if its a bank account payment method which is treated special
if (paymentAccount instanceof CountryBasedPaymentAccount) {
CountryBasedPaymentAccount countryBasedPaymentAccount = (CountryBasedPaymentAccount) paymentAccount;
// check if we have a matching country
boolean matchesCountryCodes = offer.getAcceptedCountryCodes() != null && countryBasedPaymentAccount.getCountry() != null &&
offer.getAcceptedCountryCodes().contains(countryBasedPaymentAccount.getCountry().code);
if (!matchesCountryCodes)
return false;
if (paymentAccount instanceof SepaAccount || offer.getPaymentMethod().equals(PaymentMethod.SEPA)) {
boolean samePaymentMethod = paymentAccount.getPaymentMethod().equals(offer.getPaymentMethod());
return samePaymentMethod;
} else if (offer.getPaymentMethod().equals(PaymentMethod.SAME_BANK) ||
offer.getPaymentMethod().equals(PaymentMethod.SPECIFIC_BANKS)) {
checkNotNull(offer.getAcceptedBankIds(), "offer.getAcceptedBankIds() must not be null");
if (paymentAccount instanceof SpecificBanksAccount) {
// check if we have a matching bank
boolean offerSideMatchesBank = offer.getAcceptedBankIds().contains(((BankAccount) paymentAccount).getBankId());
boolean paymentAccountSideMatchesBank = ((SpecificBanksAccount) paymentAccount).getAcceptedBanks().contains(offer.getBankId());
return offerSideMatchesBank && paymentAccountSideMatchesBank;
} else {
// national or same bank
boolean matchesBank = offer.getAcceptedBankIds().contains(((BankAccount) paymentAccount).getBankId());
return matchesBank;
}
} else {
if (paymentAccount instanceof SpecificBanksAccount) {
// check if we have a matching bank
boolean paymentAccountSideMatchesBank = ((SpecificBanksAccount) paymentAccount).getAcceptedBanks().contains(offer.getBankId());
return paymentAccountSideMatchesBank;
} else if (paymentAccount instanceof SameBankAccount) {
// check if we have a matching bank
boolean paymentAccountSideMatchesBank = ((SameBankAccount) paymentAccount).getBankId().equals(offer.getBankId());
return paymentAccountSideMatchesBank;
} else {
// national
return true;
}
}
} else {
return paymentAccount.getPaymentMethod().equals(offer.getPaymentMethod());
}
return PaymentAccountUtil.isAnyPaymentAccountValidForOffer(offer, user.getPaymentAccounts());
}
public boolean hasPaymentAccountForCurrency() {

View file

@ -26,14 +26,14 @@ import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.blockchain.BlockchainService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.pricefeed.PriceFeed;
import io.bitsquare.btc.pricefeed.PriceFeedService;
import io.bitsquare.gui.common.model.ActivatableDataModel;
import io.bitsquare.gui.main.overlays.notifications.Notification;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.payment.PaymentAccount;
import io.bitsquare.payment.PaymentAccountUtil;
import io.bitsquare.payment.PaymentMethod;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.handlers.TradeResultHandler;
@ -41,14 +41,12 @@ import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.Preferences;
import io.bitsquare.user.User;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.Fiat;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
@ -65,7 +63,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
final WalletService walletService;
private final User user;
private final Preferences preferences;
private final PriceFeed priceFeed;
private final PriceFeedService priceFeedService;
private final BlockchainService blockchainService;
private final BSFormatter formatter;
@ -104,14 +102,14 @@ class TakeOfferDataModel extends ActivatableDataModel {
@Inject
TakeOfferDataModel(TradeManager tradeManager, TradeWalletService tradeWalletService,
WalletService walletService, User user,
Preferences preferences, PriceFeed priceFeed, BlockchainService blockchainService,
Preferences preferences, PriceFeedService priceFeedService, BlockchainService blockchainService,
BSFormatter formatter) {
this.tradeManager = tradeManager;
this.tradeWalletService = tradeWalletService;
this.walletService = walletService;
this.user = user;
this.preferences = preferences;
this.priceFeed = priceFeed;
this.priceFeedService = priceFeedService;
this.blockchainService = blockchainService;
this.formatter = formatter;
@ -139,7 +137,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
// feeFromFundingTxProperty.set(FeePolicy.getMinRequiredFeeForFundingTx());
if (!preferences.getUseStickyMarketPrice() && isTabSelected)
priceFeed.setCurrencyCode(offer.getCurrencyCode());
priceFeedService.setCurrencyCode(offer.getCurrencyCode());
tradeManager.checkOfferAvailability(offer,
() -> {
@ -214,13 +212,13 @@ class TakeOfferDataModel extends ActivatableDataModel {
offer.resetState();
if (!preferences.getUseStickyMarketPrice())
priceFeed.setCurrencyCode(offer.getCurrencyCode());
priceFeedService.setCurrencyCode(offer.getCurrencyCode());
}
void onTabSelected(boolean isSelected) {
this.isTabSelected = isSelected;
if (!preferences.getUseStickyMarketPrice() && isTabSelected)
priceFeed.setCurrencyCode(offer.getCurrencyCode());
priceFeedService.setCurrencyCode(offer.getCurrencyCode());
}
@ -271,17 +269,7 @@ class TakeOfferDataModel extends ActivatableDataModel {
}
ObservableList<PaymentAccount> getPossiblePaymentAccounts() {
ObservableList<PaymentAccount> paymentAccounts = FXCollections.observableArrayList(new ArrayList<>());
for (PaymentAccount paymentAccount : user.getPaymentAccounts()) {
if (paymentAccount.getPaymentMethod().equals(offer.getPaymentMethod())) {
for (TradeCurrency tradeCurrency : paymentAccount.getTradeCurrencies()) {
if (tradeCurrency.getCode().equals(offer.getCurrencyCode())) {
paymentAccounts.add(paymentAccount);
}
}
}
}
return paymentAccounts;
return PaymentAccountUtil.getPossiblePaymentAccounts(offer, user.getPaymentAccounts());
}
boolean hasAcceptedArbitrators() {

Some files were not shown because too many files have changed in this diff Show more