mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 23:18:17 +01:00
Merge branch 'Development'
Conflicts: doc/build.md
This commit is contained in:
commit
13c2db81d7
166 changed files with 4564 additions and 965 deletions
|
@ -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>
|
||||
|
||||
|
|
35
common/src/main/java/io/bitsquare/app/Capabilities.java
Normal file
35
common/src/main/java/io/bitsquare/app/Capabilities.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package io.bitsquare.common;
|
||||
|
||||
public class CommonOptionKeys {
|
||||
public static final String LOG_LEVEL_KEY = "logLevel";
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
9
core/src/main/java/io/bitsquare/app/CoreOptionKeys.java
Normal file
9
core/src/main/java/io/bitsquare/app/CoreOptionKeys.java
Normal 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";
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
9
core/src/main/java/io/bitsquare/btc/BtcOptionKeys.java
Normal file
9
core/src/main/java/io/bitsquare/btc/BtcOptionKeys.java
Normal 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";
|
||||
}
|
28
core/src/main/java/io/bitsquare/btc/HttpClientProvider.java
Normal file
28
core/src/main/java/io/bitsquare/btc/HttpClientProvider.java
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
94
core/src/main/java/io/bitsquare/btc/ProxySocketFactory.java
Normal file
94
core/src/main/java/io/bitsquare/btc/ProxySocketFactory.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
||||
|
|
176
core/src/main/java/io/bitsquare/btc/SeedPeersSocks5Dns.java
Normal file
176
core/src/main/java/io/bitsquare/btc/SeedPeersSocks5Dns.java
Normal 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() {
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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):";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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"};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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{" +
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
},
|
193
doc/build.md
193
doc/build.md
|
@ -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?
|
||||
---------
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
99
gui/src/main/java/io/bitsquare/gui/CandleStickChart.css
Normal file
99
gui/src/main/java/io/bitsquare/gui/CandleStickChart.css
Normal 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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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("");
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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("");
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
Loading…
Add table
Reference in a new issue