forked from I2P_Developers/i2p.i2p
* Naming services, addressbook, susidns:
- Fix search capability - Fix result count and view within results - Fix published address book - Fix ngettext - Cache size - Fix 0-9 filter - Addressbook updates via API, except for published
This commit is contained in:
@ -166,10 +166,10 @@ class AddressBook {
|
||||
private static final int MAX_DEST_LENGTH = MIN_DEST_LENGTH + 100; // longer than any known cert type for now
|
||||
|
||||
/**
|
||||
* Do basic validation of the hostname and dest
|
||||
* Do basic validation of the hostname
|
||||
* hostname was already converted to lower case by ConfigParser.parse()
|
||||
*/
|
||||
private static boolean valid(String host, String dest) {
|
||||
public static boolean isValidKey(String host) {
|
||||
return
|
||||
host.endsWith(".i2p") &&
|
||||
host.length() > 4 &&
|
||||
@ -193,8 +193,15 @@ class AddressBook {
|
||||
(! host.equals("console.i2p")) &&
|
||||
(! host.endsWith(".proxy.i2p")) &&
|
||||
(! host.endsWith(".router.i2p")) &&
|
||||
(! host.endsWith(".console.i2p")) &&
|
||||
(! host.endsWith(".console.i2p"))
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do basic validation of the b64 dest, without bothering to instantiate it
|
||||
*/
|
||||
private static boolean isValidDest(String dest) {
|
||||
return
|
||||
// null cert ends with AAAA but other zero-length certs would be AA
|
||||
((dest.length() == MIN_DEST_LENGTH && dest.endsWith("AA")) ||
|
||||
(dest.length() > MIN_DEST_LENGTH && dest.length() <= MAX_DEST_LENGTH)) &&
|
||||
@ -221,7 +228,7 @@ class AddressBook {
|
||||
String otherKey = entry.getKey();
|
||||
String otherValue = entry.getValue();
|
||||
|
||||
if (valid(otherKey, otherValue)) {
|
||||
if (isValidKey(otherKey) && isValidDest(otherValue)) {
|
||||
if (this.addresses.containsKey(otherKey) && !overwrite) {
|
||||
if (!this.addresses.get(otherKey).equals(otherValue)
|
||||
&& log != null) {
|
||||
|
@ -29,6 +29,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.client.naming.NamingService;
|
||||
import net.i2p.client.naming.SingleFileNamingService;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.SecureDirectory;
|
||||
|
||||
/**
|
||||
@ -55,6 +59,7 @@ public class Daemon {
|
||||
* @param published
|
||||
* The published AddressBook. This address book is published on
|
||||
* the user's eepsite so that others may subscribe to it.
|
||||
* If non-null, overwrite with the new addressbook.
|
||||
* @param subscriptions
|
||||
* A SubscriptionList listing the remote address books to update
|
||||
* from.
|
||||
@ -75,6 +80,71 @@ public class Daemon {
|
||||
subscriptions.write();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the router and published address books using remote data from the
|
||||
* subscribed address books listed in subscriptions.
|
||||
*
|
||||
* @param router
|
||||
* The router AddressBook. This is the address book read by
|
||||
* client applications.
|
||||
* @param published
|
||||
* The published AddressBook. This address book is published on
|
||||
* the user's eepsite so that others may subscribe to it.
|
||||
* If non-null, overwrite with the new addressbook.
|
||||
* @param subscriptions
|
||||
* A SubscriptionList listing the remote address books to update
|
||||
* from.
|
||||
* @param log
|
||||
* The log to write changes and conflicts to.
|
||||
* @since 0.8.6
|
||||
*/
|
||||
public static void update(NamingService router, File published, SubscriptionList subscriptions, Log log) {
|
||||
NamingService publishedNS = null;
|
||||
Iterator<AddressBook> iter = subscriptions.iterator();
|
||||
while (iter.hasNext()) {
|
||||
// yes, the EepGet fetch() is done in next()
|
||||
AddressBook sub = iter.next();
|
||||
for (Map.Entry<String, String> entry : sub.getAddresses().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Destination oldDest = router.lookup(key);
|
||||
try {
|
||||
if (oldDest == null) {
|
||||
if (AddressBook.isValidKey(key)) {
|
||||
Destination dest = new Destination(entry.getValue());
|
||||
boolean success = router.put(key, dest);
|
||||
if (log != null) {
|
||||
if (success)
|
||||
log.append("New address " + key +
|
||||
" added to address book. From: " + sub.getLocation());
|
||||
else
|
||||
log.append("Save to naming service " + router + " failed for new key " + key);
|
||||
}
|
||||
// now update the published addressbook
|
||||
if (published != null) {
|
||||
if (publishedNS == null)
|
||||
publishedNS = new SingleFileNamingService(I2PAppContext.getGlobalContext(), published.getAbsolutePath());
|
||||
success = publishedNS.putIfAbsent(key, dest);
|
||||
if (!success)
|
||||
log.append("Save to published addressbook " + published.getAbsolutePath() + " failed for new key " + key);
|
||||
}
|
||||
} else if (log != null) {
|
||||
log.append("Bad hostname " + key + " from "
|
||||
+ sub.getLocation());
|
||||
}
|
||||
} else if (!oldDest.toBase64().equals(entry.getValue()) && log != null) {
|
||||
log.append("Conflict for " + key + " from "
|
||||
+ sub.getLocation()
|
||||
+ ". Destination in remote address book is "
|
||||
+ entry.getValue());
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
if (log != null)
|
||||
log.append("Invalid b64 for" + key + " From: " + sub.getLocation());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an update, using the Map settings to provide the parameters.
|
||||
*
|
||||
@ -120,7 +190,35 @@ public class Daemon {
|
||||
.get("proxy_host"), Integer.parseInt(settings.get("proxy_port")));
|
||||
Log log = new Log(logFile);
|
||||
|
||||
update(master, router, published, subscriptions, log);
|
||||
if (true)
|
||||
update(getNamingService(), published, subscriptions, log);
|
||||
else
|
||||
update(master, router, published, subscriptions, log);
|
||||
}
|
||||
|
||||
/** depth-first search */
|
||||
private static NamingService searchNamingService(NamingService ns, String srch)
|
||||
{
|
||||
String name = ns.getName();
|
||||
if (name == srch)
|
||||
return ns;
|
||||
List<NamingService> list = ns.getNamingServices();
|
||||
if (list != null) {
|
||||
for (NamingService nss : list) {
|
||||
NamingService rv = searchNamingService(nss, srch);
|
||||
if (rv != null)
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return the NamingService for the current file name, or the root NamingService */
|
||||
private static NamingService getNamingService()
|
||||
{
|
||||
NamingService root = I2PAppContext.getGlobalContext().namingService();
|
||||
NamingService rv = searchNamingService(root, "hosts.txt");
|
||||
return rv != null ? rv : root;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,6 +79,7 @@ public class AddressbookBean
|
||||
{
|
||||
return addressbook != null && !addressbook.isEmpty();
|
||||
}
|
||||
|
||||
public AddressbookBean()
|
||||
{
|
||||
properties = new Properties();
|
||||
@ -86,9 +87,11 @@ public class AddressbookBean
|
||||
beginIndex = 0;
|
||||
endIndex = DISPLAY_SIZE - 1;
|
||||
}
|
||||
|
||||
private long configLastLoaded = 0;
|
||||
private static final String PRIVATE_BOOK = "private_addressbook";
|
||||
private static final String DEFAULT_PRIVATE_BOOK = "../privatehosts.txt";
|
||||
|
||||
protected void loadConfig()
|
||||
{
|
||||
long currentTime = System.currentTimeMillis();
|
||||
@ -113,6 +116,7 @@ public class AddressbookBean
|
||||
try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
|
||||
public String getFileName()
|
||||
{
|
||||
loadConfig();
|
||||
@ -144,7 +148,7 @@ public class AddressbookBean
|
||||
book.compareToIgnoreCase( "router" ) != 0 &&
|
||||
book.compareToIgnoreCase( "private" ) != 0 &&
|
||||
book.compareToIgnoreCase( "published" ) != 0 ))
|
||||
book = "master";
|
||||
book = "router";
|
||||
|
||||
return book;
|
||||
}
|
||||
@ -215,40 +219,49 @@ public class AddressbookBean
|
||||
* addressbook.jsp catches the case where the whole book is empty.
|
||||
*/
|
||||
protected String generateLoadMessage() {
|
||||
String message = "";
|
||||
String message;
|
||||
String filterArg = "";
|
||||
if( search != null && search.length() > 0 ) {
|
||||
message = _("Search") + ' ';
|
||||
}
|
||||
int resultCount = resultSize();
|
||||
if( filter != null && filter.length() > 0 ) {
|
||||
if( search != null && search.length() > 0 )
|
||||
message = _("Search within filtered list") + ' ';
|
||||
message = ngettext("One result for search within filtered list.",
|
||||
"{0} results for search within filtered list.",
|
||||
resultCount);
|
||||
else
|
||||
message = _("Filtered list") + ' ';
|
||||
message = ngettext("Filtered list contains 1 entry.",
|
||||
"Fltered list contains {0} entries.",
|
||||
resultCount);
|
||||
filterArg = "&filter=" + filter;
|
||||
}
|
||||
if (entries.length == 0) {
|
||||
message += "- " + _("no matches") + '.';
|
||||
} else if (getBeginInt() == 0 && getEndInt() == entries.length - 1) {
|
||||
if (message.length() == 0)
|
||||
message = _("Addressbook") + ' ';
|
||||
if (entries.length <= 0)
|
||||
message += _("contains no entries");
|
||||
} else if( search != null && search.length() > 0 ) {
|
||||
message = ngettext("One result for search.",
|
||||
"{0} results for search.",
|
||||
resultCount);
|
||||
} else {
|
||||
if (resultCount <= 0)
|
||||
// covered in jsp
|
||||
//message = _("This addressbook is empty.");
|
||||
message = "";
|
||||
else
|
||||
message += _(entries.length, "contains 1 entry", "contains {0} entries");
|
||||
message += '.';
|
||||
message = ngettext("Addressbook contains 1 entry.",
|
||||
"Addressbook contains {0} entries.",
|
||||
resultCount);
|
||||
}
|
||||
if (resultCount <= 0) {
|
||||
// nothing to display
|
||||
} else if (getBeginInt() == 0 && getEndInt() == resultCount - 1) {
|
||||
// nothing to display
|
||||
} else {
|
||||
if (getBeginInt() > 0) {
|
||||
int newBegin = Math.max(0, getBeginInt() - DISPLAY_SIZE);
|
||||
int newEnd = Math.max(0, getBeginInt() - 1);
|
||||
message += "<a href=\"addressbook.jsp?book=" + getBook() + filterArg +
|
||||
message += " <a href=\"addressbook.jsp?book=" + getBook() + filterArg +
|
||||
"&begin=" + newBegin + "&end=" + newEnd + "\">" + newBegin +
|
||||
'-' + newEnd + "</a> | ";
|
||||
}
|
||||
message += _("Showing {0} of {1}", "" + getBegin() + '-' + getEnd(), entries.length);
|
||||
if (getEndInt() < entries.length - 1) {
|
||||
int newBegin = Math.min(entries.length - 1, getEndInt() + 1);
|
||||
int newEnd = Math.min(entries.length, getEndInt() + DISPLAY_SIZE);
|
||||
message += ' ' + _("Showing {0} of {1}", "" + getBegin() + '-' + getEnd(), Integer.valueOf(resultCount));
|
||||
if (getEndInt() < resultCount - 1) {
|
||||
int newBegin = Math.min(resultCount - 1, getEndInt() + 1);
|
||||
int newEnd = Math.min(resultCount, getEndInt() + DISPLAY_SIZE);
|
||||
message += " | <a href=\"addressbook.jsp?book=" + getBook() + filterArg +
|
||||
"&begin=" + newBegin + "&end=" + newEnd + "\">" + newBegin +
|
||||
'-' + newEnd + "</a>";
|
||||
@ -313,7 +326,8 @@ public class AddressbookBean
|
||||
if (deleted == 1)
|
||||
message = _("Destination {0} deleted.", name);
|
||||
else
|
||||
message = _("{0} destinations deleted.", deleted);
|
||||
// parameter will always be >= 2
|
||||
message = ngettext("1 destination deleted.", "{0} destinations deleted.", deleted);
|
||||
}
|
||||
}
|
||||
if( changed ) {
|
||||
@ -394,29 +408,76 @@ public class AddressbookBean
|
||||
public void setHostname(String hostname) {
|
||||
this.hostname = DataHelper.stripHTML(hostname).trim(); // XSS
|
||||
}
|
||||
|
||||
protected int getBeginInt() {
|
||||
return Math.max(0, Math.min(entries.length - 1, beginIndex));
|
||||
return Math.max(0, Math.min(resultSize() - 1, beginIndex));
|
||||
}
|
||||
|
||||
public String getBegin() {
|
||||
return "" + getBeginInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return beginning index into results
|
||||
* @since 0.8.6
|
||||
*/
|
||||
public String getResultBegin() {
|
||||
return isPrefiltered() ? "0" : Integer.toString(getBeginInt());
|
||||
}
|
||||
|
||||
public void setBegin(String s) {
|
||||
try {
|
||||
beginIndex = Integer.parseInt(s);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
|
||||
protected int getEndInt() {
|
||||
return Math.max(0, Math.max(getBeginInt(), Math.min(entries.length - 1, endIndex)));
|
||||
return Math.max(0, Math.max(getBeginInt(), Math.min(resultSize() - 1, endIndex)));
|
||||
}
|
||||
|
||||
public String getEnd() {
|
||||
return "" + getEndInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ending index into results
|
||||
* @since 0.8.6
|
||||
*/
|
||||
public String getResultEnd() {
|
||||
return Integer.toString(isPrefiltered() ? resultSize() - 1 : getEndInt());
|
||||
}
|
||||
|
||||
public void setEnd(String s) {
|
||||
try {
|
||||
endIndex = Integer.parseInt(s);
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the entries map contain only the lookup result,
|
||||
* or must we index into it?
|
||||
* @since 0.8.6
|
||||
*/
|
||||
protected boolean isPrefiltered() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size of the lookup result
|
||||
* @since 0.8.6
|
||||
*/
|
||||
protected int resultSize() {
|
||||
return entries.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the total size of the address book
|
||||
* @since 0.8.6
|
||||
*/
|
||||
protected int totalSize() {
|
||||
return entries.length;
|
||||
}
|
||||
|
||||
/** translate */
|
||||
protected static String _(String s) {
|
||||
return Messages.getString(s);
|
||||
@ -433,7 +494,7 @@ public class AddressbookBean
|
||||
}
|
||||
|
||||
/** translate (ngettext) @since 0.8.6 */
|
||||
protected static String _(int n, String s, String p) {
|
||||
protected static String ngettext(String s, String p, int n) {
|
||||
return Messages.getString(n, s, p);
|
||||
}
|
||||
}
|
||||
|
@ -37,24 +37,58 @@ import net.i2p.data.DataHelper;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
/**
|
||||
* Talk to the NamingService API instead of modifying the hosts.txt files directly
|
||||
* Talk to the NamingService API instead of modifying the hosts.txt files directly,
|
||||
* except for the 'published' addressbook.
|
||||
*
|
||||
* @since 0.8.5
|
||||
* @since 0.8.6
|
||||
*/
|
||||
public class NamingServiceBean extends AddressbookBean
|
||||
{
|
||||
private static final String DEFAULT_NS = "BlockfileNamingService";
|
||||
|
||||
private boolean isDirect() {
|
||||
return getBook().equals("published");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isPrefiltered() {
|
||||
if (isDirect())
|
||||
return super.isPrefiltered();
|
||||
return (search == null || search.length() <= 0) &&
|
||||
(filter == null || filter.length() <= 0);
|
||||
// and right naming service...
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int resultSize() {
|
||||
if (isDirect())
|
||||
return super.resultSize();
|
||||
return isPrefiltered() ? totalSize() : entries.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int totalSize() {
|
||||
if (isDirect())
|
||||
return super.totalSize();
|
||||
// only blockfile needs the list property
|
||||
Properties props = new Properties();
|
||||
props.setProperty("list", getFileName());
|
||||
return getNamingService().size(props);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNotEmpty()
|
||||
{
|
||||
return getNamingService().size() > 0;
|
||||
if (isDirect())
|
||||
return super.isNotEmpty();
|
||||
return totalSize() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName()
|
||||
{
|
||||
if (isDirect())
|
||||
return super.getFileName();
|
||||
loadConfig();
|
||||
String filename = properties.getProperty( getBook() + "_addressbook" );
|
||||
int slash = filename.lastIndexOf('/');
|
||||
@ -64,7 +98,7 @@ public class NamingServiceBean extends AddressbookBean
|
||||
}
|
||||
|
||||
/** depth-first search */
|
||||
private NamingService searchNamingService(NamingService ns, String srch)
|
||||
private static NamingService searchNamingService(NamingService ns, String srch)
|
||||
{
|
||||
String name = ns.getName();
|
||||
if (name == srch || name == DEFAULT_NS)
|
||||
@ -88,10 +122,16 @@ public class NamingServiceBean extends AddressbookBean
|
||||
return rv != null ? rv : root;
|
||||
}
|
||||
|
||||
/** Load addressbook and apply filter, returning messages about this. */
|
||||
/**
|
||||
* Load addressbook and apply filter, returning messages about this.
|
||||
* To control memory, don't load the whole addressbook if we can help it...
|
||||
* only load what is searched for.
|
||||
*/
|
||||
@Override
|
||||
public String getLoadBookMessages()
|
||||
{
|
||||
if (isDirect())
|
||||
return super.getLoadBookMessages();
|
||||
NamingService service = getNamingService();
|
||||
Debug.debug("Searching within " + service + " with filename=" + getFileName() + " and with filter=" + filter + " and with search=" + search);
|
||||
String message = "";
|
||||
@ -100,16 +140,22 @@ public class NamingServiceBean extends AddressbookBean
|
||||
Map<String, Destination> results;
|
||||
Properties searchProps = new Properties();
|
||||
// only blockfile needs this
|
||||
searchProps.setProperty("list", getFileName());
|
||||
searchProps.setProperty("list", getFileName());
|
||||
if (filter != null) {
|
||||
String startsAt = filter == "0-9" ? "0" : filter;
|
||||
String startsAt = filter.equals("0-9") ? "[0-9]" : filter;
|
||||
searchProps.setProperty("startsWith", startsAt);
|
||||
}
|
||||
if (beginIndex > 0)
|
||||
searchProps.setProperty("skip", Integer.toString(beginIndex));
|
||||
int limit = 1 + endIndex - beginIndex;
|
||||
if (limit > 0)
|
||||
searchProps.setProperty("limit", Integer.toString(limit));
|
||||
if (isPrefiltered()) {
|
||||
// Only limit if we not searching or filtering, so we will
|
||||
// know the total number of results
|
||||
if (beginIndex > 0)
|
||||
searchProps.setProperty("skip", Integer.toString(beginIndex));
|
||||
int limit = 1 + endIndex - beginIndex;
|
||||
if (limit > 0)
|
||||
searchProps.setProperty("limit", Integer.toString(limit));
|
||||
}
|
||||
if (search != null && search.length() > 0)
|
||||
searchProps.setProperty("search", search.toLowerCase());
|
||||
results = service.getEntries(searchProps);
|
||||
|
||||
Debug.debug("Result count: " + results.size());
|
||||
@ -151,6 +197,8 @@ public class NamingServiceBean extends AddressbookBean
|
||||
@Override
|
||||
public String getMessages()
|
||||
{
|
||||
if (isDirect())
|
||||
return super.getMessages();
|
||||
// Loading config and addressbook moved into getLoadBookMessages()
|
||||
String message = "";
|
||||
|
||||
@ -168,23 +216,22 @@ public class NamingServiceBean extends AddressbookBean
|
||||
} else if (oldDest != null && !action.equals(_("Replace"))) {
|
||||
message = _("Host name {0} is already in addressbook with a different destination. Click \"Replace\" to overwrite.", hostname);
|
||||
} else {
|
||||
boolean valid = true;
|
||||
try {
|
||||
Destination dest = new Destination(destination);
|
||||
getNamingService().put(hostname, dest, nsOptions);
|
||||
boolean success = getNamingService().put(hostname, dest, nsOptions);
|
||||
if (success) {
|
||||
changed = true;
|
||||
if (oldDest == null)
|
||||
message = _("Destination added for {0}.", hostname);
|
||||
else
|
||||
message = _("Destination changed for {0}.", hostname);
|
||||
// clear form
|
||||
hostname = null;
|
||||
destination = null;
|
||||
} else {
|
||||
message = _("Failed to add Destination for {0} to naming service {1}", hostname, getNamingService()) + "<br>";
|
||||
}
|
||||
} catch (DataFormatException dfe) {
|
||||
valid = false;
|
||||
}
|
||||
if (valid) {
|
||||
changed = true;
|
||||
if (oldDest == null)
|
||||
message = _("Destination added for {0}.", hostname);
|
||||
else
|
||||
message = _("Destination changed for {0}.", hostname);
|
||||
// clear form
|
||||
hostname = null;
|
||||
destination = null;
|
||||
} else {
|
||||
message = _("Invalid Base 64 destination.");
|
||||
}
|
||||
}
|
||||
@ -197,17 +244,20 @@ public class NamingServiceBean extends AddressbookBean
|
||||
String name = null;
|
||||
int deleted = 0;
|
||||
for (String n : deletionMarks) {
|
||||
getNamingService().remove(n, nsOptions);
|
||||
if (deleted++ == 0) {
|
||||
boolean success = getNamingService().remove(n, nsOptions);
|
||||
if (!success) {
|
||||
message += _("Failed to delete Destination for {0} from naming service {1}", name, getNamingService()) + "<br>";
|
||||
} else if (deleted++ == 0) {
|
||||
changed = true;
|
||||
name = n;
|
||||
}
|
||||
}
|
||||
if( changed ) {
|
||||
if (deleted == 1)
|
||||
message = _("Destination {0} deleted.", name);
|
||||
message += _("Destination {0} deleted.", name);
|
||||
else
|
||||
message = _("{0} destinations deleted.", deleted);
|
||||
// parameter will always be >= 2
|
||||
message = ngettext("1 destination deleted.", "{0} destinations deleted.", deleted);
|
||||
}
|
||||
}
|
||||
if( changed ) {
|
||||
|
@ -144,7 +144,7 @@ ${book.loadBookMessages}
|
||||
<th><%=intl._("Destination")%></th>
|
||||
</tr>
|
||||
<!-- limit iterator, or "Form too large" may result on submit, and is a huge web page if we don't -->
|
||||
<c:forEach items="${book.entries}" var="addr" begin="${book.begin}" end="${book.end}">
|
||||
<c:forEach items="${book.entries}" var="addr" begin="${book.resultBegin}" end="${book.resultEnd}">
|
||||
<tr class="list${book.trClass}">
|
||||
<c:if test="${book.master || book.router || book.published || book.private}">
|
||||
<td class="checkbox"><input type="checkbox" name="checked" value="${addr.name}" title="<%=intl._("Mark for deletion")%>"></td>
|
||||
|
@ -476,13 +476,17 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
* 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
|
||||
* Don't use both startsWith and beginWith.
|
||||
* Search, startsWith, and beginWith values must be lower case.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Destination> getEntries(Properties options) {
|
||||
String listname = FALLBACK_LIST;
|
||||
String search = null;
|
||||
String startsWith = null;
|
||||
String beginWith = null;
|
||||
int limit = Integer.MAX_VALUE;
|
||||
@ -491,10 +495,15 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
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)
|
||||
beginWith = startsWith;
|
||||
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);
|
||||
@ -505,7 +514,9 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
} catch (NumberFormatException nfe) {}
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Searching " + listname + " beginning with " + beginWith + " starting with " + startsWith + " limit=" + limit + " skip=" + skip);
|
||||
_log.debug("Searching " + listname + " beginning with " + beginWith +
|
||||
" starting with " + startsWith + " search string " + search +
|
||||
" limit=" + limit + " skip=" + skip);
|
||||
synchronized(_bf) {
|
||||
try {
|
||||
SkipList sl = _bf.getIndex(listname, _stringSerializer, _destSerializer);
|
||||
@ -523,12 +534,21 @@ public class BlockfileNamingService extends DummyNamingService {
|
||||
for (int i = 0; i < skip && iter.hasNext(); i++) {
|
||||
iter.next();
|
||||
}
|
||||
for (int i = 0; i < limit && iter.hasNext(); i++) {
|
||||
String key = (String) iter.nextKey();
|
||||
if (startsWith != null && !key.startsWith(startsWith))
|
||||
break;
|
||||
DestEntry de = (DestEntry) iter.next();
|
||||
rv.put(key, de.dest);
|
||||
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 (search != null && key.indexOf(search) < 0)
|
||||
continue;
|
||||
rv.put(key, de.dest);
|
||||
i++;
|
||||
}
|
||||
return rv;
|
||||
} catch (IOException ioe) {
|
||||
|
@ -53,6 +53,10 @@ public class SingleFileNamingService extends NamingService {
|
||||
private final static Log _log = new Log(SingleFileNamingService.class);
|
||||
private final File _file;
|
||||
private final ReentrantReadWriteLock _fileLock;
|
||||
/** cached number of entries */
|
||||
private int _size;
|
||||
/** last write time */
|
||||
private long _lastWrite;
|
||||
|
||||
public SingleFileNamingService(I2PAppContext context, String filename) {
|
||||
super(context);
|
||||
@ -292,14 +296,18 @@ public class SingleFileNamingService extends NamingService {
|
||||
|
||||
/**
|
||||
* @param options As follows:
|
||||
* Key "search": return only those matching substring
|
||||
* Key "startsWith": return only those starting with
|
||||
* ("[0-9]" allowed)
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Destination> getEntries(Properties options) {
|
||||
if (!_file.exists())
|
||||
return Collections.EMPTY_MAP;
|
||||
String startsWith = "";
|
||||
String searchOpt = null;
|
||||
String startsWith = null;
|
||||
if (options != null) {
|
||||
searchOpt = options.getProperty("search");
|
||||
startsWith = options.getProperty("startsWith");
|
||||
}
|
||||
BufferedReader in = null;
|
||||
@ -307,11 +315,19 @@ public class SingleFileNamingService extends NamingService {
|
||||
try {
|
||||
in = new BufferedReader(new InputStreamReader(new FileInputStream(_file), "UTF-8"), 16*1024);
|
||||
String line = null;
|
||||
String search = startsWith + '=';
|
||||
String search = startsWith == null ? null : startsWith + '=';
|
||||
Map<String, Destination> rv = new HashMap();
|
||||
while ( (line = in.readLine()) != null) {
|
||||
if ((!startsWith.equals("")) && !line.startsWith(search))
|
||||
if (line.length() <= 0)
|
||||
continue;
|
||||
if (search != null) {
|
||||
if (startsWith.equals("[0-9]")) {
|
||||
if (line.charAt(0) < '0' || line.charAt(0) > '9')
|
||||
continue;
|
||||
} else if (!line.startsWith(search)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (line.startsWith("#"))
|
||||
continue;
|
||||
if (line.indexOf('#') > 0) // trim off any end of line comment
|
||||
@ -320,12 +336,18 @@ public class SingleFileNamingService extends NamingService {
|
||||
if (split <= 0)
|
||||
continue;
|
||||
String key = line.substring(split);
|
||||
if (searchOpt != null && key.indexOf(searchOpt) < 0)
|
||||
continue;
|
||||
String b64 = line.substring(split+1); //.trim() ??????????????
|
||||
try {
|
||||
Destination dest = new Destination(b64);
|
||||
rv.put(key, dest);
|
||||
} catch (DataFormatException dfe) {}
|
||||
}
|
||||
if (searchOpt == null && startsWith == null) {
|
||||
_lastWrite = _file.lastModified();
|
||||
_size = rv.size();
|
||||
}
|
||||
return rv;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("getEntries error", ioe);
|
||||
@ -346,14 +368,18 @@ public class SingleFileNamingService extends NamingService {
|
||||
BufferedReader in = null;
|
||||
getReadLock();
|
||||
try {
|
||||
if (_file.lastModified() <= _lastWrite)
|
||||
return _size;
|
||||
in = new BufferedReader(new InputStreamReader(new FileInputStream(_file), "UTF-8"), 16*1024);
|
||||
String line = null;
|
||||
int rv = 0;
|
||||
while ( (line = in.readLine()) != null) {
|
||||
if (line.startsWith("#"))
|
||||
if (line.startsWith("#") || line.length() <= 0)
|
||||
continue;
|
||||
rv++;
|
||||
}
|
||||
_lastWrite = _file.lastModified();
|
||||
_size = rv;
|
||||
return rv;
|
||||
} catch (IOException ioe) {
|
||||
_log.error("size() error", ioe);
|
||||
|
Reference in New Issue
Block a user