package jelectrum.db.level;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.io.DataInputStream;
import java.util.Random;
import java.text.DecimalFormat;
import java.util.Map;
import java.util.TreeMap;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;
import java.util.concurrent.LinkedBlockingQueue;
import jelectrum.db.DBTooManyResultsException;
import jelectrum.EventLog;
import jelectrum.Config;
import com.google.protobuf.ByteString;
public class LevelNetClient
{
public static final int RESULT_GOOD = 1273252631;
public static final int RESULT_BAD = 9999;
public static final int RESULT_TOO_MANY = 28365921;
public static final int RESULT_NOTFOUND = 133133;
public static final int SOCKET_TIMEOUT = 45000;
public static final byte OP_MAX_RESULTS=1;
private EventLog log;
private Config config;
private String host;
private int port;
private LinkedBlockingQueue<LevelConnection> conns;
private Semaphore open_sem;
public boolean throw_on_error=false;
public LevelNetClient(EventLog log, Config config) throws Exception
{
this.log = log;
this.config = config;
config.require("leveldb_host");
config.require("leveldb_port");
config.require("leveldb_conns");
this.host = config.get("leveldb_host");
this.port = config.getInt("leveldb_port");
open_sem = new Semaphore(config.getInt("leveldb_conns"));
conns = new LinkedBlockingQueue<LevelConnection>();
new MaintThread().start();
}
private LevelConnection getConnection()
{
LevelConnection conn = conns.poll();
if (conn != null) return conn;
if (open_sem.tryAcquire())
{
while(true)
{
try
{
conn = new LevelConnection();
return conn;
}
catch(java.io.IOException e)
{
log.alarm("LevelDB connection failure: " + e);
if (throw_on_error) throw new RuntimeException(e);
try{ Thread.sleep(2500); } catch(Throwable t){}
}
}
}
try
{
return conns.take();
}
catch(InterruptedException e)
{
throw new RuntimeException(e);
}
}
private void returnConnection(LevelConnection conn)
{
if (conn == null) return;
try
{
conns.put(conn);
}
catch(InterruptedException e)
{
throw new RuntimeException(e);
}
}
private void trashConnection(LevelConnection conn)
{
if (conn != null)
{
open_sem.release(1);
conn.close();
}
}
public ByteString get(String key)
{
//System.out.println("leveldb loading: " + key);
while(true)
{
LevelConnection conn = null;
try
{
conn = getConnection();
return conn.get(key);
}
catch(java.io.IOException e)
{
trashConnection(conn); conn=null;
log.log("LevelDB error: " + e);
if (throw_on_error) throw new RuntimeException(e);
try{ Thread.sleep(2500); } catch(Throwable t){}
}
finally
{
returnConnection(conn);
}
}
}
public void put(String key, ByteString value)
{
while(true)
{
LevelConnection conn = null;
try
{
conn = getConnection();
conn.put(key, value);
return;
}
catch(java.io.IOException e)
{
trashConnection(conn); conn=null;
log.log("LevelDB error: " + e);
if (throw_on_error) throw new RuntimeException(e);
try{ Thread.sleep(2500); } catch(Throwable t){}
}
finally
{
returnConnection(conn);
}
}
}
public void putAll(Map<String, ByteString> m)
{
while(true)
{
LevelConnection conn = null;
try
{
conn = getConnection();
conn.putAll(m);
return;
}
catch(java.io.IOException e)
{
trashConnection(conn); conn=null;
log.log("LevelDB error: " + e);
if (throw_on_error) throw new RuntimeException(e);
try{ Thread.sleep(2500); } catch(Throwable t){}
}
finally
{
returnConnection(conn);
}
}
}
public Map<String, ByteString> getByPrefix(String prefix, int max_results)
{
while(true)
{
LevelConnection conn = null;
try
{
conn = getConnection();
return conn.getByPrefix(prefix, max_results);
}
catch(java.io.IOException e)
{
trashConnection(conn); conn=null;
log.log("LevelDB error: " + e);
if (throw_on_error) throw new RuntimeException(e);
try{ Thread.sleep(2500); } catch(Throwable t){}
}
finally
{
returnConnection(conn);
}
}
}
public class LevelConnection
{
public LevelConnection()
throws java.io.IOException
{
try
{
sock = new Socket(host, port);
sock.setTcpNoDelay(true);
sock.setSoTimeout(SOCKET_TIMEOUT);
d_in = new DataInputStream(sock.getInputStream());
}
catch(java.lang.SecurityException e)
{
throw new RuntimeException(e);
}
}
private Socket sock;
private DataInputStream d_in;
public void close()
{
try
{
sock.close();
}
catch(Throwable t){}
}
public ByteString get(String key)
throws java.io.IOException
{
byte cmd[]=new byte[2];
cmd[0]=1;
cmd[1]=0;
sock.getOutputStream().write(cmd, 0, 2);
writeString(key);
sock.getOutputStream().flush();
int status = readInt();
if (status == RESULT_NOTFOUND) return null;
else if (status != RESULT_GOOD) throw new java.io.IOException("Bad result: " + status);
return readBytes();
}
public void ping()
throws java.io.IOException
{
byte cmd[]=new byte[2];
cmd[0]=5;
cmd[1]=0;
sock.getOutputStream().write(cmd, 0, 2);
int status = readInt();
if (status != RESULT_GOOD) throw new java.io.IOException("Bad result: " + status);
}
public void put(String key, ByteString value)
throws java.io.IOException
{
byte cmd[]=new byte[2];
cmd[0]=2;
cmd[1]=0;
sock.getOutputStream().write(cmd, 0, 2);
writeString(key);
writeByteArray(value);
sock.getOutputStream().flush();
int status = readInt();
if (status != RESULT_GOOD) throw new java.io.IOException("Bad result: " + status);
}
public void putAll(Map<String, ByteString> map)
throws java.io.IOException
{
byte cmd[]=new byte[2];
cmd[0]=3;
cmd[1]=0;
sock.getOutputStream().write(cmd, 0, 2);
writeInt(map.size());
for(Map.Entry<String, ByteString> me : map.entrySet())
{
writeString(me.getKey());
writeByteArray(me.getValue());
}
int status = readInt();
if (status != RESULT_GOOD) throw new java.io.IOException("Bad result: " + status);
}
public Map<String, ByteString> getByPrefix(String prefix,int max_results)
throws java.io.IOException
{
byte cmd[]=new byte[2];
cmd[0]=4;
cmd[1]=OP_MAX_RESULTS;
sock.getOutputStream().write(cmd, 0, 2);
writeInt(max_results);
writeString(prefix);
int status = readInt();
if (status == RESULT_TOO_MANY)
{
throw new DBTooManyResultsException();
}
int items = readInt();
Map<String,ByteString> m = new TreeMap<>();
for(int i=0; i<items; i++)
{
String key = readString();
ByteString buff = readBytes();
m.put(key, buff);
}
return m;
}
private ByteString readBytes()
throws java.io.IOException
{
int sz = readInt();
//if (sz == 0) return ByteString;
byte[] data = new byte[sz];
d_in.readFully(data);
return ByteString.copyFrom(data);
}
private String readString()
throws java.io.IOException
{
return readBytes().toStringUtf8();
}
private int readInt()
throws java.io.IOException
{
byte[] d = new byte[4];
d_in.readFully(d);
ByteBuffer bb = ByteBuffer.wrap(d);
bb.order(java.nio.ByteOrder.BIG_ENDIAN);
return bb.getInt();
}
private void writeNull()
throws java.io.IOException
{
writeInt(0);
}
private void writeInt(int val)
throws java.io.IOException
{
byte[] val_bytes=new byte[4];
ByteBuffer bb = ByteBuffer.wrap(val_bytes);
bb.order(java.nio.ByteOrder.BIG_ENDIAN);
bb.putInt(val);
sock.getOutputStream().write(val_bytes);
}
private void writeString(String s)
throws java.io.IOException
{
if (s == null)
{
writeNull();
return;
}
byte[] bytes = s.getBytes();
writeInt(bytes.length);
sock.getOutputStream().write(bytes);
}
private void writeByteArray(ByteString bb)
throws java.io.IOException
{
if (bb == null)
{
writeNull();
return;
}
byte[] bytes = bb.toByteArray();
writeInt(bb.size());
bb.writeTo(sock.getOutputStream());
}
}
public class MaintThread extends Thread
{
public MaintThread()
{
setName("LevelNetClient/MaintThread");
setDaemon(true);
}
public void run()
{
while(true)
{
try
{
Thread.sleep(30000);
int open = conns.size();
//log.log("Levelnetclient: checking " + open + " connections");
for(int i=0; i<open; i++)
{
LevelConnection conn = null;
conn = conns.poll();
if (conn != null)
{
try
{
conn.ping();
returnConnection(conn);
//log.log("Levelnetclient: ping ok");
}
catch(java.io.IOException e)
{
log.log("Levelnetclient: exception: " + e);
trashConnection(conn);
}
}
}
while(open_sem.tryAcquire())
{
try
{
LevelConnection conn = null;
conn = new LevelConnection();
returnConnection(conn);
}
catch(Throwable t)
{
log.log("Levelnetclient: " + t);
open_sem.release(1);
}
}
}
catch(Throwable t)
{
log.log("LevelNetClient/MaintThread - " + t);
}
}
}
}
}