package com.msgilligan.bitcoinj.cli;
import com.msgilligan.bitcoinj.rpc.JsonRPCException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.ParseException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import com.msgilligan.bitcoinj.rpc.BitcoinClient;
import com.msgilligan.bitcoinj.rpc.RPCConfig;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
/**
* Base class for CLI commands that use Bitcoin RPC
*/
public abstract class CliCommand {
static final String defaultproto = "http";
static final String defaulthost = "127.0.0.1";
static final int defaultport = 8332;
static final String defaultfile = "/";
protected CommandLine line = null;
protected CommandLineParser parser = null;
protected CliOptions options;
protected String name;
protected String usage;
protected HelpFormatter formatter = null;
protected InputStream in;
protected PrintWriter pwout;
protected PrintWriter pwerr;
protected BitcoinClient client = null;
protected CliCommand(String name, CliOptions options, String[] args) {
this(name, null, options, args);
}
protected CliCommand(String name, String usage, CliOptions options, String[] args) {
this.name = name;
if (usage != null) {
this.usage = usage;
} else {
this.usage = name;
}
this.options = options;
parser = new DefaultParser();
try {
this.line = this.parser.parse(options, args);
} catch (ParseException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public BitcoinClient getClient() {
if (client == null) {
System.out.println("Connecting to: " + getRPCConfig().getURI());
RPCConfig config = getRPCConfig();
client = new BitcoinClient( config.getNetParams(),
config.getURI(),
config.getUsername(),
config.getPassword());
}
return client;
}
/**
* Can be used by subclasses to set a narrower type
* @param client An instance of BitcoinClient or subclass
*/
protected void setClient(BitcoinClient client) {
this.client = client;
}
public void printHelp() {
if (formatter == null) {
formatter = new HelpFormatter();
formatter.setLongOptPrefix("-");
}
int leftPad = 4;
int descPad = 2;
int helpWidth = 120;
String header = "";
String footer = "";
formatter.printHelp(pwout, helpWidth, usage, header, options, leftPad, descPad, footer, false);
}
public void printError(String str) {
pwout.println(str);
}
/**
* Check Options and Arguments
*
* Override to customize behavior.
*
* @return status code
*/
public Integer checkArgs() {
if (line.hasOption("?")) {
printHelp();
// Return 1 so tool can exit
return 1;
}
return 0;
}
/**
* Initial a client and if rpcwait option set, make sure server is accepting connections.
* @return status code
*/
public Integer preflight() {
getClient();
if (line.hasOption("rpcwait")) {
boolean available = false; // Wait up to 1 hour
try {
available = client.waitForServer(60*60);
} catch (JsonRPCException e) {
this.pwerr.println("JSON-RPC Exception: " + e.getMessage());
return 1;
}
if (!available) {
this.pwerr.println("Timeout error.");
return 1;
}
}
return 0;
}
public Integer run() {
return run(System.in, System.out, System.err);
}
public Integer run(InputStream in, PrintStream out, PrintStream err) {
this.in = in;
this.pwout = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), true);
this.pwerr = new PrintWriter(new OutputStreamWriter(err, StandardCharsets.UTF_8), true);
Integer status = checkArgs();
if (status != 0) {
return status;
}
status = preflight();
if (status != 0) {
return status;
}
try {
status = runImpl();
} catch (IOException e) {
e.printStackTrace();
status = 1;
} catch (JsonRPCException e) {
e.printStackTrace();
status = 1;
}
return status;
}
/**
* Implement in subclasses
* @return status code
*/
abstract protected Integer runImpl() throws IOException, JsonRPCException;
private URI getServerURI() {
String proto = defaultproto;
String host = defaulthost;
int port = defaultport;
String file = defaultfile;
if (line.hasOption("rpcssl")) {
proto = "https";
}
if (line.hasOption("rpcconnect")) {
host = line.getOptionValue("rpcconnect", defaulthost);
}
if (line.hasOption("regtest") || line.hasOption("testnet")) {
port = 18332;
}
if (line.hasOption("rpcport")) {
String portString = line.getOptionValue("rpcport");
port = Integer.parseInt(portString);
}
URI rpcServerURI = null;
try {
rpcServerURI = new URI(proto, null, host, port, file, null, null);
} catch (URISyntaxException e) {
// We should be careful that this never happens
e.printStackTrace();
// But if it does, throw an unchecked exception
throw new RuntimeException(e);
}
return rpcServerURI;
}
protected RPCConfig getRPCConfig() {
URI uri = getServerURI();
String user = line.getOptionValue("rpcuser", "");
String pass = line.getOptionValue("rpcpassword", "");
NetworkParameters netParams;
if (line.hasOption("regtest")) {
netParams = RegTestParams.get();
} else if (line.hasOption(("testnet"))) {
netParams = TestNet3Params.get();
} else {
netParams = MainNetParams.get();
}
RPCConfig cfg = new RPCConfig(netParams, uri, user, pass);
return cfg;
}
}