forked from I2P_Developers/i2p.i2p
stub out new API, needs testing
This commit is contained in:
@@ -335,8 +335,7 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
return rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Destination lookup(String hostname) {
|
||||
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
|
||||
Destination d = super.lookup(hostname);
|
||||
if (d != null)
|
||||
return d;
|
||||
|
@@ -7,6 +7,12 @@
|
||||
*/
|
||||
package net.i2p.client.naming;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
@@ -14,8 +20,12 @@ import net.i2p.data.Destination;
|
||||
* A Dummy naming service that can only handle base64 and b32 destinations.
|
||||
*/
|
||||
class DummyNamingService extends NamingService {
|
||||
private final Map<String, CacheEntry> _cache;
|
||||
|
||||
private static final int BASE32_HASH_LENGTH = 52; // 1 + Hash.HASH_LENGTH * 8 / 5
|
||||
public final static String PROP_B32 = "i2p.naming.hostsTxt.useB32";
|
||||
protected static final int CACHE_MAX_SIZE = 16;
|
||||
public static final int DEST_SIZE = 516; // Std. Base64 length (no certificate)
|
||||
|
||||
/**
|
||||
* The naming service should only be constructed and accessed through the
|
||||
@@ -23,11 +33,13 @@ class DummyNamingService extends NamingService {
|
||||
* appropriate application context itself.
|
||||
*
|
||||
*/
|
||||
protected DummyNamingService(I2PAppContext context) { super(context); }
|
||||
private DummyNamingService() { super(null); }
|
||||
protected DummyNamingService(I2PAppContext context) {
|
||||
super(context);
|
||||
_cache = new HashMap(CACHE_MAX_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Destination lookup(String hostname) {
|
||||
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
|
||||
Destination d = getCache(hostname);
|
||||
if (d != null)
|
||||
return d;
|
||||
@@ -52,4 +64,90 @@ class DummyNamingService extends NamingService {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide basic caching for the service
|
||||
* The service may override the age and/or size limit
|
||||
*/
|
||||
/** Don't know why a dest would ever change but keep this short anyway */
|
||||
protected static final long CACHE_MAX_AGE = 7*60*1000;
|
||||
|
||||
private class CacheEntry {
|
||||
public Destination dest;
|
||||
public long exp;
|
||||
public CacheEntry(Destination d) {
|
||||
dest = d;
|
||||
exp = _context.clock().now() + CACHE_MAX_AGE;
|
||||
}
|
||||
public boolean isExpired() {
|
||||
return exp < _context.clock().now();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up when full.
|
||||
* Don't bother removing old entries unless full.
|
||||
* Caller must synchronize on _cache.
|
||||
*/
|
||||
private void cacheClean() {
|
||||
if (_cache.size() < CACHE_MAX_SIZE)
|
||||
return;
|
||||
boolean full = true;
|
||||
String oldestKey = null;
|
||||
long oldestExp = Long.MAX_VALUE;
|
||||
List<String> deleteList = new ArrayList(CACHE_MAX_SIZE);
|
||||
for (Map.Entry<String, CacheEntry> entry : _cache.entrySet()) {
|
||||
CacheEntry ce = entry.getValue();
|
||||
if (ce.isExpired()) {
|
||||
deleteList.add(entry.getKey());
|
||||
full = false;
|
||||
continue;
|
||||
}
|
||||
if (oldestKey == null || ce.exp < oldestExp) {
|
||||
oldestKey = entry.getKey();
|
||||
oldestExp = ce.exp;
|
||||
}
|
||||
}
|
||||
if (full && oldestKey != null)
|
||||
deleteList.add(oldestKey);
|
||||
for (String s : deleteList) {
|
||||
_cache.remove(s);
|
||||
}
|
||||
}
|
||||
|
||||
protected void putCache(String s, Destination d) {
|
||||
if (d == null)
|
||||
return;
|
||||
synchronized (_cache) {
|
||||
_cache.put(s, new CacheEntry(d));
|
||||
cacheClean();
|
||||
}
|
||||
}
|
||||
|
||||
protected Destination getCache(String s) {
|
||||
synchronized (_cache) {
|
||||
CacheEntry ce = _cache.get(s);
|
||||
if (ce == null)
|
||||
return null;
|
||||
if (ce.isExpired()) {
|
||||
_cache.remove(s);
|
||||
return null;
|
||||
}
|
||||
return ce.dest;
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.8.5 */
|
||||
protected void removeCache(String s) {
|
||||
synchronized (_cache) {
|
||||
_cache.remove(s);
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.8.1 */
|
||||
public void clearCache() {
|
||||
synchronized (_cache) {
|
||||
_cache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ import net.i2p.data.Destination;
|
||||
* i2p.naming.eepget.list=http://stats.i2p/cgi-bin/hostquery.cgi?a=,http://i2host.i2p/cgi-bin/i2hostquery?
|
||||
*
|
||||
* @author zzz
|
||||
* @deprecated use HostsTxtNamingService.put()
|
||||
* @since 0.7.9
|
||||
*/
|
||||
public class EepGetAndAddNamingService extends EepGetNamingService {
|
||||
|
@@ -7,6 +7,7 @@ package net.i2p.client.naming;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
@@ -25,6 +26,7 @@ import net.i2p.util.Log;
|
||||
* Should be used from MetaNamingService, after HostsTxtNamingService.
|
||||
* Cannot be used as the only NamingService! Be sure any naming service hosts
|
||||
* are in hosts.txt.
|
||||
* Supports caching, b32, and b64.
|
||||
*
|
||||
* Sample config to put in configadvanced.jsp (restart required):
|
||||
*
|
||||
@@ -33,7 +35,7 @@ import net.i2p.util.Log;
|
||||
* i2p.naming.eepget.list=http://namingservice.i2p/cgi-bin/lkup.cgi?host=,http://i2host.i2p/cgi-bin/i2hostquery?
|
||||
*
|
||||
*/
|
||||
public class EepGetNamingService extends NamingService {
|
||||
public class EepGetNamingService extends DummyNamingService {
|
||||
|
||||
private final static String PROP_EEPGET_LIST = "i2p.naming.eepget.list";
|
||||
private final static String DEFAULT_EEPGET_LIST = "http://i2host.i2p/cgi-bin/i2hostquery?";
|
||||
@@ -59,22 +61,13 @@ public class EepGetNamingService extends NamingService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Destination lookup(String hostname) {
|
||||
// If it's long, assume it's a key.
|
||||
if (hostname.length() >= DEST_SIZE)
|
||||
return lookupBase64(hostname);
|
||||
|
||||
hostname = hostname.toLowerCase();
|
||||
|
||||
// If you want b32, chain with HostsTxtNamingService
|
||||
if (hostname.length() == 60 && hostname.endsWith(".b32.i2p"))
|
||||
return null;
|
||||
|
||||
// check the cache
|
||||
Destination d = getCache(hostname);
|
||||
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
|
||||
Destination d = super.lookup(hostname, null, null);
|
||||
if (d != null)
|
||||
return d;
|
||||
|
||||
hostname = hostname.toLowerCase();
|
||||
|
||||
List URLs = getURLs();
|
||||
if (URLs.isEmpty())
|
||||
return null;
|
||||
@@ -103,7 +96,6 @@ public class EepGetNamingService extends NamingService {
|
||||
}
|
||||
|
||||
// FIXME allow larger Dests for non-null Certs
|
||||
private static final int DEST_SIZE = 516; // Std. Base64 length (no certificate)
|
||||
private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff
|
||||
private String fetchAddr(String url, String hostname) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(MAX_RESPONSE);
|
||||
|
@@ -5,6 +5,7 @@
|
||||
package net.i2p.client.naming;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
@@ -27,6 +28,7 @@ import net.i2p.util.Log;
|
||||
*
|
||||
* Can be used from MetaNamingService, (e.g. after HostsTxtNamingService),
|
||||
* or as the sole naming service.
|
||||
* Supports caching, b32, and b64.
|
||||
*
|
||||
* Sample chained config to put in configadvanced.jsp (restart required):
|
||||
*
|
||||
@@ -40,7 +42,7 @@ import net.i2p.util.Log;
|
||||
* i2p.naming.exec.command=/usr/local/bin/i2presolve
|
||||
*
|
||||
*/
|
||||
public class ExecNamingService extends NamingService {
|
||||
public class ExecNamingService extends DummyNamingService {
|
||||
|
||||
private final static String PROP_EXEC_CMD = "i2p.naming.exec.command";
|
||||
private final static String DEFAULT_EXEC_CMD = "/usr/local/bin/i2presolve";
|
||||
@@ -59,22 +61,13 @@ public class ExecNamingService extends NamingService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Destination lookup(String hostname) {
|
||||
// If it's long, assume it's a key.
|
||||
if (hostname.length() >= DEST_SIZE)
|
||||
return lookupBase64(hostname);
|
||||
|
||||
hostname = hostname.toLowerCase();
|
||||
|
||||
// If you want b32, chain with HostsTxtNamingService
|
||||
if (hostname.length() == 60 && hostname.endsWith(".b32.i2p"))
|
||||
return null;
|
||||
|
||||
// check the cache
|
||||
Destination d = getCache(hostname);
|
||||
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
|
||||
Destination d = super.lookup(hostname, null, null);
|
||||
if (d != null)
|
||||
return d;
|
||||
|
||||
hostname = hostname.toLowerCase();
|
||||
|
||||
// lookup
|
||||
String key = fetchAddr(hostname);
|
||||
if (key != null) {
|
||||
@@ -87,7 +80,6 @@ public class ExecNamingService extends NamingService {
|
||||
}
|
||||
|
||||
// FIXME allow larger Dests for non-null Certs
|
||||
private static final int DEST_SIZE = 516; // Std. Base64 length (no certificate)
|
||||
private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff
|
||||
private String fetchAddr(String hostname) {
|
||||
String[] commandArr = new String[3];
|
||||
|
@@ -7,29 +7,21 @@
|
||||
*/
|
||||
package net.i2p.client.naming;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.Hash;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* A naming service based on the "hosts.txt" file.
|
||||
* A naming service based on multiple "hosts.txt" files.
|
||||
* Supports .b32.i2p and {b64} lookups.
|
||||
* Supports caching.
|
||||
* All host names are converted to lower case.
|
||||
*/
|
||||
public class HostsTxtNamingService extends DummyNamingService {
|
||||
public class HostsTxtNamingService extends MetaNamingService {
|
||||
|
||||
/**
|
||||
* The naming service should only be constructed and accessed through the
|
||||
@@ -37,8 +29,12 @@ public class HostsTxtNamingService extends DummyNamingService {
|
||||
* appropriate application context itself.
|
||||
*
|
||||
*/
|
||||
public HostsTxtNamingService(I2PAppContext context) { super(context); }
|
||||
private HostsTxtNamingService() { super(null); }
|
||||
public HostsTxtNamingService(I2PAppContext context) {
|
||||
super(context, null);
|
||||
for (String name : getFilenames()) {
|
||||
addNamingService(new SingleFileNamingService(context, name), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this system property is specified, the tunnel will read the
|
||||
@@ -46,132 +42,39 @@ public class HostsTxtNamingService extends DummyNamingService {
|
||||
*/
|
||||
public final static String PROP_HOSTS_FILE = "i2p.hostsfilelist";
|
||||
|
||||
/** default hosts.txt filename */
|
||||
/** default hosts.txt filenames */
|
||||
public final static String DEFAULT_HOSTS_FILE =
|
||||
"privatehosts.txt,userhosts.txt,hosts.txt";
|
||||
|
||||
private final static Log _log = new Log(HostsTxtNamingService.class);
|
||||
|
||||
private List getFilenames() {
|
||||
private List<String> getFilenames() {
|
||||
String list = _context.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE);
|
||||
StringTokenizer tok = new StringTokenizer(list, ",");
|
||||
List rv = new ArrayList(tok.countTokens());
|
||||
List<String> rv = new ArrayList(tok.countTokens());
|
||||
while (tok.hasMoreTokens())
|
||||
rv.add(tok.nextToken());
|
||||
return rv;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Destination lookup(String hostname) {
|
||||
Destination d = super.lookup(hostname);
|
||||
if (d != null)
|
||||
return d;
|
||||
|
||||
List filenames = getFilenames();
|
||||
for (int i = 0; i < filenames.size(); i++) {
|
||||
String hostsfile = (String)filenames.get(i);
|
||||
try {
|
||||
File f = new File(_context.getRouterDir(), hostsfile);
|
||||
if ( (f.exists()) && (f.canRead()) ) {
|
||||
String key = getKey(f, hostname.toLowerCase());
|
||||
if ( (key != null) && (key.trim().length() > 0) ) {
|
||||
d = lookupBase64(key);
|
||||
putCache(hostname, d);
|
||||
return d;
|
||||
}
|
||||
|
||||
} else {
|
||||
_log.warn("Hosts file " + hostsfile + " does not exist.");
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
_log.error("Error loading hosts file " + hostsfile, ioe);
|
||||
}
|
||||
// not found, continue to the next file
|
||||
}
|
||||
return null;
|
||||
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
|
||||
// If it's long, assume it's a key.
|
||||
if (hostname.length() >= DEST_SIZE)
|
||||
return lookupBase64(hostname);
|
||||
return super.lookup(hostname.toLowerCase(), lookupOptions, storedOptions);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String reverseLookup(Destination dest) {
|
||||
String destkey = dest.toBase64();
|
||||
List filenames = getFilenames();
|
||||
for (int i = 0; i < filenames.size(); i++) {
|
||||
String hostsfile = (String)filenames.get(i);
|
||||
Properties hosts = new Properties();
|
||||
try {
|
||||
File f = new File(_context.getRouterDir(), hostsfile);
|
||||
if ( (f.exists()) && (f.canRead()) ) {
|
||||
DataHelper.loadProps(hosts, f, true);
|
||||
Set keyset = hosts.keySet();
|
||||
Iterator iter = keyset.iterator();
|
||||
while (iter.hasNext()) {
|
||||
String host = (String)iter.next();
|
||||
String key = hosts.getProperty(host);
|
||||
if (destkey.equals(key))
|
||||
return host;
|
||||
}
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
_log.error("Error loading hosts file " + hostsfile, ioe);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
public boolean put(String hostname, Destination d, Properties options) {
|
||||
return super.put(hostname.toLowerCase(), d, options);
|
||||
}
|
||||
|
||||
/** @deprecated unused */
|
||||
@Override
|
||||
public String reverseLookup(Hash h) {
|
||||
List filenames = getFilenames();
|
||||
for (int i = 0; i < filenames.size(); i++) {
|
||||
String hostsfile = (String)filenames.get(i);
|
||||
Properties hosts = new Properties();
|
||||
try {
|
||||
File f = new File(_context.getRouterDir(), hostsfile);
|
||||
if ( (f.exists()) && (f.canRead()) ) {
|
||||
DataHelper.loadProps(hosts, f, true);
|
||||
Set keyset = hosts.keySet();
|
||||
Iterator iter = keyset.iterator();
|
||||
while (iter.hasNext()) {
|
||||
String host = (String)iter.next();
|
||||
String key = hosts.getProperty(host);
|
||||
try {
|
||||
Destination destkey = new Destination();
|
||||
destkey.fromBase64(key);
|
||||
if (h.equals(destkey.calculateHash()))
|
||||
return host;
|
||||
} catch (DataFormatException dfe) {}
|
||||
}
|
||||
}
|
||||
} catch (Exception ioe) {
|
||||
_log.error("Error loading hosts file " + hostsfile, ioe);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
|
||||
return super.putIfAbsent(hostname.toLowerCase(), d, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Better than DataHelper.loadProps(), doesn't load the whole file into memory,
|
||||
* and stops when it finds a match.
|
||||
*
|
||||
* @param host lower case
|
||||
* @since 0.7.13
|
||||
*/
|
||||
private static String getKey(File file, String host) throws IOException {
|
||||
BufferedReader in = null;
|
||||
try {
|
||||
in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"), 16*1024);
|
||||
String line = null;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
if (!line.toLowerCase().startsWith(host + '='))
|
||||
continue;
|
||||
if (line.indexOf('#') > 0) // trim off any end of line comment
|
||||
line = line.substring(0, line.indexOf('#')).trim();
|
||||
int split = line.indexOf('=');
|
||||
return line.substring(split+1); //.trim() ??????????????
|
||||
}
|
||||
} finally {
|
||||
if (in != null) try { in.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
return null;
|
||||
@Override
|
||||
public boolean remove(String hostname, Properties options) {
|
||||
return super.remove(hostname.toLowerCase(), options);
|
||||
}
|
||||
}
|
||||
|
@@ -2,61 +2,154 @@ package net.i2p.client.naming;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
public class MetaNamingService extends NamingService {
|
||||
/**
|
||||
* A naming service of multiple naming services.
|
||||
* Supports .b32.i2p and {b64} lookups.
|
||||
* Supports caching.
|
||||
*/
|
||||
public class MetaNamingService extends DummyNamingService {
|
||||
|
||||
private final static String PROP_NAME_SERVICES = "i2p.nameservicelist";
|
||||
private final static String DEFAULT_NAME_SERVICES =
|
||||
"net.i2p.client.naming.PetNameNamingService,net.i2p.client.naming.HostsTxtNamingService";
|
||||
private List _services;
|
||||
"net.i2p.client.naming.HostsTxtNamingService";
|
||||
|
||||
protected final List<NamingService> _services;
|
||||
|
||||
public MetaNamingService(I2PAppContext context) {
|
||||
super(context);
|
||||
|
||||
String list = _context.getProperty(PROP_NAME_SERVICES, DEFAULT_NAME_SERVICES);
|
||||
StringTokenizer tok = new StringTokenizer(list, ",");
|
||||
_services = new ArrayList(tok.countTokens());
|
||||
_services = new CopyOnWriteArrayList();
|
||||
while (tok.hasMoreTokens()) {
|
||||
try {
|
||||
Class cls = Class.forName(tok.nextToken());
|
||||
Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class });
|
||||
_services.add(con.newInstance(new Object[] { context }));
|
||||
addNamingService((NamingService)con.newInstance(new Object[] { context }), false);
|
||||
} catch (Exception ex) {
|
||||
_services.add(new DummyNamingService(context)); // fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param if non-null, services to be added. If null, this will only handle b32 and b64.
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public MetaNamingService(I2PAppContext context, List<NamingService> services) {
|
||||
super(context);
|
||||
_services = new CopyOnWriteArrayList();
|
||||
if (services != null) {
|
||||
for (NamingService ns : services) {
|
||||
addNamingService(ns, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Destination lookup(String hostname) {
|
||||
Iterator iter = _services.iterator();
|
||||
while (iter.hasNext()) {
|
||||
NamingService ns = (NamingService)iter.next();
|
||||
Destination dest = ns.lookup(hostname);
|
||||
if (dest != null) {
|
||||
return dest;
|
||||
public boolean addNamingService(NamingService ns, boolean head) {
|
||||
if (head)
|
||||
_services.add(0, ns);
|
||||
else
|
||||
_services.add(ns);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NamingService> getNamingServices() {
|
||||
return new Collections.unmodifiableList(_services);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeNamingService(NamingService ns) {
|
||||
return _services.remove(ns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener(NamingServiceListener nsl) {
|
||||
for (NamingService ns : _services) {
|
||||
ns.registerListener(nsl);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterListener(NamingServiceListener nsl) {
|
||||
for (NamingService ns : _services) {
|
||||
ns.unregisterListener(nsl);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) {
|
||||
Destination d = super.lookup(hostname, null, null);
|
||||
if (d != null)
|
||||
return d;
|
||||
|
||||
for (NamingService ns : _services) {
|
||||
d = ns.lookup(hostname, lookupOptions, storedOptions);
|
||||
if (d != null) {
|
||||
putCache(hostname, d);
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return lookupBase64(hostname);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String reverseLookup(Destination dest) {
|
||||
Iterator iter = _services.iterator();
|
||||
while (iter.hasNext()) {
|
||||
NamingService ns = (NamingService)iter.next();
|
||||
String hostname = ns.reverseLookup(dest);
|
||||
if (hostname != null) {
|
||||
return hostname;
|
||||
public String reverseLookup(Destination dest, Properties options) {
|
||||
for (NamingService ns : _services) {
|
||||
String host = ns.reverseLookup(dest, options);
|
||||
if (host != null) {
|
||||
return host;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores in the last service
|
||||
*/
|
||||
@Override
|
||||
public boolean put(String hostname, Destination d, Properties options) {
|
||||
if (_services.isEmpty())
|
||||
return false;
|
||||
boolean rv = _services.get(_services.size() - 1).put(hostname, d, options);
|
||||
// overwrite any previous entry in case it changed
|
||||
if (rv)
|
||||
putCache(hostname, d);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores in the last service
|
||||
*/
|
||||
@Override
|
||||
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
|
||||
if (_services.isEmpty())
|
||||
return false;
|
||||
return _services.get(_services.size() - 1).putIfAbsent(hostname, d, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from all services
|
||||
*/
|
||||
@Override
|
||||
public boolean remove(String hostname, Properties options) {
|
||||
boolean rv = false;
|
||||
for (NamingService ns : _services) {
|
||||
if (ns.remove(hostname, options))
|
||||
rv = true;
|
||||
}
|
||||
if (rv)
|
||||
removeCache(hostname);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
@@ -9,9 +9,13 @@ package net.i2p.client.naming;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.DataFormatException;
|
||||
@@ -25,15 +29,12 @@ import net.i2p.util.Log;
|
||||
public abstract class NamingService {
|
||||
|
||||
private final static Log _log = new Log(NamingService.class);
|
||||
protected I2PAppContext _context;
|
||||
private /* FIXME final FIXME */ HashMap _cache;
|
||||
protected final I2PAppContext _context;
|
||||
protected final Set<NamingServiceListener> _listeners;
|
||||
|
||||
/** what classname should be used as the naming service impl? */
|
||||
public static final String PROP_IMPL = "i2p.naming.impl";
|
||||
private static final String DEFAULT_IMPL = "net.i2p.client.naming.HostsTxtNamingService";
|
||||
|
||||
protected static final int CACHE_MAX_SIZE = 16;
|
||||
|
||||
|
||||
/**
|
||||
* The naming service should only be constructed and accessed through the
|
||||
@@ -43,9 +44,7 @@ public abstract class NamingService {
|
||||
*/
|
||||
protected NamingService(I2PAppContext context) {
|
||||
_context = context;
|
||||
_cache = new HashMap(CACHE_MAX_SIZE);
|
||||
}
|
||||
private NamingService() { // nop
|
||||
_listeners = new CopyOnWriteArraySet();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +52,9 @@ public abstract class NamingService {
|
||||
* @return the Destination for this host name, or
|
||||
* <code>null</code> if name is unknown.
|
||||
*/
|
||||
public abstract Destination lookup(String hostname);
|
||||
public Destination lookup(String hostname) {
|
||||
return lookup(hostname, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse look up a destination
|
||||
@@ -61,10 +62,12 @@ public abstract class NamingService {
|
||||
* if none is known. It is safe for subclasses to always return
|
||||
* <code>null</code> if no reverse lookup is possible.
|
||||
*/
|
||||
public String reverseLookup(Destination dest) { return null; };
|
||||
public String reverseLookup(Destination dest) {
|
||||
return reverseLookup(dest, null);
|
||||
}
|
||||
|
||||
/** @deprecated unused */
|
||||
public String reverseLookup(Hash h) { return null; };
|
||||
public String reverseLookup(Hash h) { return null; }
|
||||
|
||||
/**
|
||||
* Check if host name is valid Base64 encoded dest and return this
|
||||
@@ -82,6 +85,313 @@ public abstract class NamingService {
|
||||
}
|
||||
}
|
||||
|
||||
///// New API Starts Here
|
||||
|
||||
/**
|
||||
* @return Class simple name by default
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public String getName() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NamingService-specific options or null
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public Properties getConfiguration() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public boolean setConfiguration(Properties p) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// These are for daisy chaining (MetaNamingService)
|
||||
|
||||
/**
|
||||
* @return chained naming services or null
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public List<NamingService> getNamingServices() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return parent naming service or null if this is the root
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public NamingService getParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only for chaining-capable NamingServices. Add to end of the list.
|
||||
* @return success
|
||||
*/
|
||||
public boolean addNamingService(NamingService ns) {
|
||||
return addNamingService(ns, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Only for chaining-capable NamingServices
|
||||
* @param head or tail
|
||||
* @return success
|
||||
*/
|
||||
public boolean addNamingService(NamingService ns, boolean head) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only for chaining-capable NamingServices
|
||||
* @return success
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public boolean removeNamingService(NamingService ns) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// options would be used to specify public / private / master ...
|
||||
// or should we just daisy chain 3 HostsTxtNamingServices ?
|
||||
// that might be better... then addressbook only talks to the 'router' HostsTxtNamingService
|
||||
|
||||
/**
|
||||
* @return number of entries or -1 if unknown
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public int size() {
|
||||
return size(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options NamingService-specific, can be null
|
||||
* @return number of entries (matching the options if non-null) or -1 if unknown
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public int size(Properties options) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all mappings
|
||||
* or empty Map if none;
|
||||
* Returned Map is not necessarily sorted, implementation dependent
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public Map<String, Destination> getEntries() {
|
||||
return getEntries(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options NamingService-specific, can be null
|
||||
* @return all mappings (matching the options if non-null)
|
||||
* or empty Map if none;
|
||||
* Returned Map is not necessarily sorted, implementation dependent
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public Map<String, Destination> getEntries(Properties options) {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
/**
|
||||
* This may be more or less efficient than getEntries()
|
||||
* @param options NamingService-specific, can be null
|
||||
* @return all mappings (matching the options if non-null)
|
||||
* or empty Map if none;
|
||||
* Returned Map is not necessarily sorted, implementation dependent
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public Map<String, String> getBase64Entries(Properties options) {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all known host names
|
||||
* or empty Set if none;
|
||||
* Returned Set is not necessarily sorted, implementation dependent
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public Set<String> getNames() {
|
||||
return getNames(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options NamingService-specific, can be null
|
||||
* @return all known host names (matching the options if non-null)
|
||||
* or empty Set if none;
|
||||
* Returned Set is not necessarily sorted, implementation dependent
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public Set<String> getNames(Properties options) {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public boolean put(String hostname, Destination d) {
|
||||
return put(hostname, d, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options NamingService-specific, can be null
|
||||
* @return success
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public boolean put(String hostname, Destination d, Properties options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fails if entry previously exists
|
||||
* @return success
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public boolean putIfAbsent(String hostname, Destination d) {
|
||||
return putIfAbsent(hostname, d, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fails if entry previously exists
|
||||
* @param options NamingService-specific, can be null
|
||||
* @return success
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public boolean putIfAbsent(String hostname, Destination d, Properties options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options NamingService-specific, can be null
|
||||
* @return success
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public boolean putAll(Map<String, Destination> entries, Properties options) {
|
||||
boolean rv = true;
|
||||
for (Map.Entry<String, Destination> entry : entries.entrySet()) {
|
||||
if (!put(entry.getKey(), entry.getValue(), options))
|
||||
rv = false;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fails if entry did not previously exist
|
||||
* @param d may be null if only options are changing
|
||||
* @param options NamingService-specific, can be null
|
||||
* @return success
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public boolean update(String hostname, Destination d, Properties options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return success
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public boolean remove(String hostname) {
|
||||
return remove(hostname, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options NamingService-specific, can be null
|
||||
* @return success
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public boolean remove(String hostname, Properties options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the NamingService to update its database
|
||||
* Should this be a separate interface? This is what addressbook needs
|
||||
* @param options NamingService-specific, can be null
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public void requestUpdate(Properties options) {}
|
||||
|
||||
/**
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public void registerListener(NamingServiceListener nsl) {
|
||||
_listeners.add(nsl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public void unregisterListener(NamingServiceListener nsl) {
|
||||
_listeners.remove(nsl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as lookup(hostname) but with in and out options
|
||||
* Note that whether this (and lookup(hostname)) resolve B32 addresses is
|
||||
* NamingService-specific.
|
||||
* @param lookupOptions input parameter, NamingService-specific, can be null
|
||||
* @param storedOptions output parameter, NamingService-specific, any stored properties will be added if non-null
|
||||
* @return dest or null
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public abstract Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions);
|
||||
|
||||
/**
|
||||
* Same as reverseLookup(dest) but with options
|
||||
* @param options NamingService-specific, can be null
|
||||
* @return host name or null
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public String reverseLookup(Destination d, Properties options) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a Base 32 address. This may require the router to fetch the LeaseSet,
|
||||
* which may take quite a while.
|
||||
* @param hostname must be {52 chars}.b32.i2p
|
||||
* @param timeout in seconds; <= 0 means use router default
|
||||
* @return dest or null
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public Destination lookupBase32(String hostname, int timeout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as lookupB32 but with the SHA256 Hash precalculated
|
||||
* @param timeout in seconds; <= 0 means use router default
|
||||
* @return dest or null
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public Destination lookup(Hash hash, int timeout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent will call when added.
|
||||
* If this is the root naming service, the core will start it.
|
||||
* Should not be called by others.
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public void start() {}
|
||||
|
||||
/**
|
||||
* Parent will call when removed.
|
||||
* If this is the root naming service, the core will stop it.
|
||||
* Should not be called by others.
|
||||
* @since 0.8.5
|
||||
*/
|
||||
public void stop() {}
|
||||
|
||||
//// End New API
|
||||
|
||||
/**
|
||||
* Get a naming service instance. This method ensures that there
|
||||
* will be only one naming service instance (singleton) as well as
|
||||
@@ -102,83 +412,4 @@ public abstract class NamingService {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide basic caching for the service
|
||||
* The service may override the age and/or size limit
|
||||
*/
|
||||
/** Don't know why a dest would ever change but keep this short anyway */
|
||||
protected static final long CACHE_MAX_AGE = 7*60*1000;
|
||||
|
||||
private class CacheEntry {
|
||||
public Destination dest;
|
||||
public long exp;
|
||||
public CacheEntry(Destination d) {
|
||||
dest = d;
|
||||
exp = _context.clock().now() + CACHE_MAX_AGE;
|
||||
}
|
||||
public boolean isExpired() {
|
||||
return exp < _context.clock().now();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up when full.
|
||||
* Don't bother removing old entries unless full.
|
||||
* Caller must synchronize on _cache.
|
||||
*/
|
||||
private void cacheClean() {
|
||||
if (_cache.size() < CACHE_MAX_SIZE)
|
||||
return;
|
||||
boolean full = true;
|
||||
Object oldestKey = null;
|
||||
long oldestExp = Long.MAX_VALUE;
|
||||
ArrayList deleteList = new ArrayList(CACHE_MAX_SIZE);
|
||||
for (Iterator iter = _cache.entrySet().iterator(); iter.hasNext(); ) {
|
||||
Map.Entry entry = (Map.Entry) iter.next();
|
||||
CacheEntry ce = (CacheEntry) entry.getValue();
|
||||
if (ce.isExpired()) {
|
||||
deleteList.add(entry.getKey());
|
||||
full = false;
|
||||
continue;
|
||||
}
|
||||
if (oldestKey == null || ce.exp < oldestExp) {
|
||||
oldestKey = entry.getKey();
|
||||
oldestExp = ce.exp;
|
||||
}
|
||||
}
|
||||
if (full && oldestKey != null)
|
||||
deleteList.add(oldestKey);
|
||||
for (Iterator iter = deleteList.iterator(); iter.hasNext(); ) {
|
||||
_cache.remove(iter.next());
|
||||
}
|
||||
}
|
||||
|
||||
protected void putCache(String s, Destination d) {
|
||||
if (d == null)
|
||||
return;
|
||||
synchronized (_cache) {
|
||||
_cache.put(s, new CacheEntry(d));
|
||||
cacheClean();
|
||||
}
|
||||
}
|
||||
|
||||
protected Destination getCache(String s) {
|
||||
synchronized (_cache) {
|
||||
CacheEntry ce = (CacheEntry) _cache.get(s);
|
||||
if (ce == null)
|
||||
return null;
|
||||
if (ce.isExpired()) {
|
||||
_cache.remove(s);
|
||||
return null;
|
||||
}
|
||||
return ce.dest;
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 0.8.1 */
|
||||
public void clearCache() {
|
||||
synchronized (_cache) {
|
||||
_cache.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user