stub out new API, needs testing

This commit is contained in:
zzz
2011-02-22 23:39:51 +00:00
parent 258c260601
commit c269546c08
8 changed files with 581 additions and 272 deletions

View File

@@ -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;

View File

@@ -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();
}
}
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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];

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}