NamingService: Add export methods,

fill in subclass methods for efficient exporting,
fill in getBase64Entires() and getNames() where unimplemented
SusiDNS: Add export support, no UI yet
This commit is contained in:
zzz
2015-04-27 15:41:38 +00:00
parent f243968dfa
commit eaac4d3de0
7 changed files with 439 additions and 4 deletions

View File

@ -18,11 +18,14 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;
import net.i2p.I2PAppContext;
import net.i2p.data.DataFormatException;
@ -860,7 +863,7 @@ public class BlockfileNamingService extends DummyNamingService {
iter = sl.find(beginWith);
else
iter = sl.iterator();
Map<String, Destination> rv = new HashMap<String, Destination>();
Map<String, Destination> rv = new TreeMap<String, Destination>();
for (int i = 0; i < skip && iter.hasNext(); i++) {
// don't bother validating here
iter.next();
@ -896,6 +899,188 @@ public class BlockfileNamingService extends DummyNamingService {
}
}
/**
* @param options If non-null and contains the key "list", get
* from that list (default "hosts.txt", NOT all lists)
* Key "skip": skip that many entries
* Key "limit": max number to return
* Key "search": return only those matching substring
* Key "startsWith": return only those starting with
* ("[0-9]" allowed)
* Key "beginWith": start here in the iteration
* Don't use both startsWith and beginWith.
* Search, startsWith, and beginWith values must be lower case.
* @since 0.9.20
*/
@Override
public Map<String, String> getBase64Entries(Properties options) {
String listname = FALLBACK_LIST;
String search = null;
String startsWith = null;
String beginWith = null;
int limit = Integer.MAX_VALUE;
int skip = 0;
if (options != null) {
String ln = options.getProperty("list");
if (ln != null)
listname = ln;
search = options.getProperty("search");
startsWith = options.getProperty("startsWith");
beginWith = options.getProperty("beginWith");
if (beginWith == null && startsWith != null) {
if (startsWith.equals("[0-9]"))
beginWith = "0";
else
beginWith = startsWith;
}
String lim = options.getProperty("limit");
try {
limit = Integer.parseInt(lim);
} catch (NumberFormatException nfe) {}
String sk = options.getProperty("skip");
try {
skip = Integer.parseInt(sk);
} catch (NumberFormatException nfe) {}
}
synchronized(_bf) {
if (_isClosed)
return Collections.emptyMap();
try {
SkipList sl = _bf.getIndex(listname, _stringSerializer, _destSerializer);
if (sl == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("No skiplist found for lookup in " + listname);
return Collections.emptyMap();
}
SkipIterator iter;
if (beginWith != null)
iter = sl.find(beginWith);
else
iter = sl.iterator();
Map<String, String> rv = new TreeMap<String, String>();
for (int i = 0; i < skip && iter.hasNext(); i++) {
// don't bother validating here
iter.next();
}
for (int i = 0; i < limit && iter.hasNext(); ) {
String key = (String) iter.nextKey();
if (startsWith != null) {
if (startsWith.equals("[0-9]")) {
if (key.charAt(0) > '9')
break;
} else if (!key.startsWith(startsWith)) {
break;
}
}
DestEntry de = (DestEntry) iter.next();
if (!validate(key, de, listname))
continue;
if (search != null && key.indexOf(search) < 0)
continue;
rv.put(key, de.dest.toBase64());
i++;
}
return rv;
} catch (IOException ioe) {
_log.error("DB lookup error", ioe);
return Collections.emptyMap();
} catch (RuntimeException re) {
_log.error("DB lookup error", re);
return Collections.emptyMap();
} finally {
deleteInvalid();
}
}
}
/**
* @param options If non-null and contains the key "list", get
* from that list (default "hosts.txt", NOT all lists)
* Key "skip": skip that many entries
* Key "limit": max number to return
* Key "search": return only those matching substring
* Key "startsWith": return only those starting with
* ("[0-9]" allowed)
* Key "beginWith": start here in the iteration
* Don't use both startsWith and beginWith.
* Search, startsWith, and beginWith values must be lower case.
* @since 0.9.20
*/
@Override
public Set<String> getNames(Properties options) {
String listname = FALLBACK_LIST;
String search = null;
String startsWith = null;
String beginWith = null;
int limit = Integer.MAX_VALUE;
int skip = 0;
if (options != null) {
String ln = options.getProperty("list");
if (ln != null)
listname = ln;
search = options.getProperty("search");
startsWith = options.getProperty("startsWith");
beginWith = options.getProperty("beginWith");
if (beginWith == null && startsWith != null) {
if (startsWith.equals("[0-9]"))
beginWith = "0";
else
beginWith = startsWith;
}
String lim = options.getProperty("limit");
try {
limit = Integer.parseInt(lim);
} catch (NumberFormatException nfe) {}
String sk = options.getProperty("skip");
try {
skip = Integer.parseInt(sk);
} catch (NumberFormatException nfe) {}
}
synchronized(_bf) {
if (_isClosed)
return Collections.emptySet();
try {
SkipList sl = _bf.getIndex(listname, _stringSerializer, _destSerializer);
if (sl == null) {
if (_log.shouldLog(Log.WARN))
_log.warn("No skiplist found for lookup in " + listname);
return Collections.emptySet();
}
SkipIterator iter;
if (beginWith != null)
iter = sl.find(beginWith);
else
iter = sl.iterator();
Set<String> rv = new HashSet<String>();
for (int i = 0; i < skip && iter.hasNext(); i++) {
iter.next();
}
for (int i = 0; i < limit && iter.hasNext(); ) {
String key = (String) iter.nextKey();
if (startsWith != null) {
if (startsWith.equals("[0-9]")) {
if (key.charAt(0) > '9')
break;
} else if (!key.startsWith(startsWith)) {
break;
}
}
if (search != null && key.indexOf(search) < 0)
continue;
rv.add(key);
i++;
}
return rv;
} catch (IOException ioe) {
_log.error("DB lookup error", ioe);
return Collections.emptySet();
} catch (RuntimeException re) {
_log.error("DB lookup error", re);
return Collections.emptySet();
}
}
}
/**
* @param options ignored
* @since 0.8.9

View File

@ -1,5 +1,7 @@
package net.i2p.client.naming;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.HashMap;
@ -179,6 +181,19 @@ public class MetaNamingService extends DummyNamingService {
return rv;
}
/**
* All services aggregated
* @since 0.9.20
*/
@Override
public Map<String, String> getBase64Entries(Properties options) {
Map<String, String> rv = new HashMap<String, String>();
for (NamingService ns : _services) {
rv.putAll(ns.getBase64Entries(options));
}
return rv;
}
/**
* All services aggregated
*/
@ -191,6 +206,17 @@ public class MetaNamingService extends DummyNamingService {
return rv;
}
/**
* All services aggregated.
* Duplicates not removed (for efficiency)
* @since 0.9.20
*/
public void export(Writer out, Properties options) throws IOException {
for (NamingService ns : _services) {
export(out, options);
}
}
/**
* All services aggregated
*/

View File

@ -7,12 +7,16 @@
*/
package net.i2p.client.naming;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArraySet;
import net.i2p.I2PAppContext;
@ -235,14 +239,75 @@ public abstract class NamingService {
* Warning - This will bring the whole database into memory
* if options is null, empty, or unsupported, use with caution.
*
* This implementation calls getEntries(options) and returns a SortedMap.
* Subclasses should override if they store base64 natively.
*
* @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.7
* @since 0.8.7, implemented in 0.9.20
*/
public Map<String, String> getBase64Entries(Properties options) {
return Collections.emptyMap();
Map<String, Destination> entries = getEntries(options);
if (entries.size() <= 0)
return Collections.emptyMap();
Map<String, String> rv = new TreeMap<String, String>();
for (Map.Entry<String, Destination> e : entries.entrySet()) {
rv.put(e.getKey(), e.getValue().toBase64());
}
return rv;
}
/**
* Export in a hosts.txt format.
* Output is not necessarily sorted, implementation dependent.
* Output may or may not contain comment lines, implementation dependent.
* Caller must close writer.
*
* This implementation calls getBase64Entries().
* Subclasses should override if they store in a hosts.txt format natively.
*
* @since 0.9.20
*/
public void export(Writer out) throws IOException {
export(out, null);
}
/**
* Export in a hosts.txt format.
* Output is not necessarily sorted, implementation dependent.
* Output may or may not contain comment lines, implementation dependent.
* Caller must close writer.
*
* This implementation calls getBase64Entries(options).
* Subclasses should override if they store in a hosts.txt format natively.
*
* @param options NamingService-specific, can be null
* @since 0.9.20
*/
public void export(Writer out, Properties options) throws IOException {
Map<String, String> entries = getBase64Entries(options);
out.write("# Address book: ");
out.write(getName());
out.write('\n');
int sz = entries.size();
if (sz <= 0) {
out.write("# No entries\n");
return;
}
out.write("# Exported: ");
out.write((new Date()).toString());
out.write('\n');
if (sz > 1) {
out.write("# " + sz + " entries\n");
}
for (Map.Entry<String, String> e : entries.entrySet()) {
out.write(e.getKey());
out.write('=');
out.write(e.getValue());
out.write('\n');
}
}
/**

View File

@ -15,7 +15,9 @@ import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ -364,6 +366,102 @@ public class SingleFileNamingService extends NamingService {
}
}
/**
* Overridden since we store base64 natively.
*
* @param options As follows:
* Key "search": return only those matching substring
* Key "startsWith": return only those starting with
* ("[0-9]" allowed)
* @return all mappings (matching the options if non-null)
* or empty Map if none.
* Returned Map is not sorted.
* @since 0.9.20
*/
public Map<String, String> getBase64Entries(Properties options) {
if (!_file.exists())
return Collections.emptyMap();
String searchOpt = null;
String startsWith = null;
if (options != null) {
searchOpt = options.getProperty("search");
startsWith = options.getProperty("startsWith");
}
BufferedReader in = null;
getReadLock();
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(_file), "UTF-8"), 16*1024);
String line = null;
Map<String, String> rv = new HashMap<String, String>();
while ( (line = in.readLine()) != null) {
if (line.length() <= 0)
continue;
if (startsWith != null) {
if (startsWith.equals("[0-9]")) {
if (line.charAt(0) < '0' || line.charAt(0) > '9')
continue;
} else if (!line.startsWith(startsWith)) {
continue;
}
}
if (line.startsWith("#"))
continue;
if (line.indexOf('#') > 0) // trim off any end of line comment
line = line.substring(0, line.indexOf('#')).trim();
int split = line.indexOf('=');
if (split <= 0)
continue;
String key = line.substring(0, split);
if (searchOpt != null && key.indexOf(searchOpt) < 0)
continue;
String b64 = line.substring(split+1); //.trim() ??????????????
if (b64.length() < 387)
continue;
rv.put(key, b64);
}
if (searchOpt == null && startsWith == null) {
_lastWrite = _file.lastModified();
_size = rv.size();
}
return rv;
} catch (IOException ioe) {
_log.error("getEntries error", ioe);
return Collections.emptyMap();
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
releaseReadLock();
}
}
/**
* Overridden for efficiency.
* Output is not sorted.
*
* @param options ignored
* @since 0.9.20
*/
public void export(Writer out, Properties options) throws IOException {
out.write("# Address book: ");
out.write(getName());
out.write('\n');
out.write("# Exported: ");
out.write((new Date()).toString());
out.write('\n');
BufferedReader in = null;
getReadLock();
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream(_file), "UTF-8"), 16*1024);
String line = null;
while ( (line = in.readLine()) != null) {
out.write(line);
out.write('\n');
}
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
releaseReadLock();
}
}
/**
* @param options ignored
* @return all known host names, unsorted