Update: Partial implementation of su3 news with atom feed.

No spec yet, just followed str4d's testnews.atom.xml proposal.
Atom parsing is tested, su3 part is incomplete and untested.
Todo: add spec to http://i2p-projekt.i2p/en/docs/spec/updates,
finish su3 and test.
This commit is contained in:
zzz
2014-10-21 18:35:06 +00:00
parent 86c43f4734
commit 239fe518a9
14 changed files with 716 additions and 11 deletions

View File

@ -81,6 +81,24 @@
</javac>
</target>
<!-- newsxml.jar only (subset of routerconsole, no war) for Android -->
<target name="compileNewsOnly" depends="prepare, depend, dependVersion">
<mkdir dir="./build" />
<mkdir dir="./build/obj" />
<javac
srcdir="./src"
includes="net/i2p/router/news/*.java"
debug="true" deprecation="on" source="${javac.version}" target="${javac.version}"
includeAntRuntime="false"
destdir="./build/obj">
<compilerarg line="${javac.compilerargs}" />
<classpath>
<pathelement location="../../../core/java/build/i2p.jar" />
<pathelement location="../../../router/java/build/router.jar" />
</classpath>
</javac>
</target>
<!-- the jar with the latest message classes from the jsps, and the war too -->
<target name="jar" depends="jar1, war, bundle" />
@ -119,6 +137,12 @@
</jar>
</target>
<!-- newsxml.jar only (subset of routerconsole, no war) for Android -->
<target name="newsxmljar" depends="compileNewsOnly" >
<jar destfile="./build/newsxml.jar" basedir="./build/obj" includes="**/*.class">
</jar>
</target>
<!-- this is tricky because the message classes go in the jar, not in the war -->
<target name="bundle" depends="jar1, precompilejsp" unless="no.bundle">
<!-- Update the messages_*.po files.

View File

@ -0,0 +1,27 @@
package net.i2p.router.news;
/**
* One news item.
* Any String fields may be null.
*
* @since 0.9.17
*/
public class NewsEntry implements Comparable<NewsEntry> {
public String title;
public String link;
public String id;
public long updated;
public String summary;
public String content;
public String contentType; // attribute of content
public String authorName; // subnode of author
/** reverse, newest first */
public int compareTo(NewsEntry e) {
if (updated > e.updated)
return -1;
if (updated < e.updated)
return 1;
return 0;
}
}

View File

@ -0,0 +1,28 @@
package net.i2p.router.news;
import java.util.List;
/**
* The update metadata.
* Any String or List fields may be null.
*
* @since 0.9.17
*/
public class NewsMetadata {
// Standard Atom feed metadata
public String feedTitle;
public String feedSubtitle;
public String feedID;
public long feedUpdated;
// I2P update metadata
public long date;
public String minVersion;
public String minJavaVersion;
public String i2pVersion;
public String sudTorrent;
public String su2Torrent;
public String su3Torrent;
public List<String> su3Clearnet;
public List<String> su3SSL;
}

View File

@ -0,0 +1,352 @@
package net.i2p.router.news;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.util.Log;
import org.cybergarage.xml.Node;
import org.cybergarage.xml.ParserException;
import org.cybergarage.xml.parser.JaxpParser;
/**
* Parse out the news.xml file which is in Atom format (RFC4287).
*
* We use the XML parser from the UPnP library.
*
* @since 0.9.17
*/
public class NewsXMLParser {
private final I2PAppContext _context;
private final Log _log;
private List<NewsEntry> _entries;
private NewsMetadata _metadata;
private XHTMLMode _mode;
private static final Set xhtmlWhitelist = new HashSet(Arrays.asList(new String[] {
"a", "b", "br", "div", "i", "p", "span", "font", "blockquote", "hr",
"del", "ins", "em", "strong", "mark", "sub", "sup", "tt", "code", "strike", "s", "u",
"h4", "h5", "h6",
"ol", "ul", "li", "dl", "dt", "dd",
"table", "tr", "td", "th"
}));
/**
* The action taken when encountering a non-whitelisted
* XHTML element in the feed content.
*/
public enum XHTMLMode {
/** abort the parsing on any non-whitelisted element */
ABORT,
/** remove only the non-whitelisted element */
REMOVE_ELEMENT,
/** skip the feed entry containing the non-whitelisted element */
SKIP_ENTRY,
/** disable whitelist checks */
ALLOW_ALL
}
public NewsXMLParser(I2PAppContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(NewsXMLParser.class);
_mode = XHTMLMode.REMOVE_ELEMENT;
}
/**
* Sets the action taken when encountering a non-whitelisted
* XHTML element in the feed content.
* Must be set before parse().
* Default REMOVE_ELEMENT.
*/
public void setXHTMLMode(XHTMLMode mode) {
_mode = mode;
}
/**
* Process the XML file.
*
* @param file XML content only. Any su3 or gunzip handling must have
* already happened.
* @throws IOException on any parse error
*/
public void parse(File file) throws IOException {
parse(new BufferedInputStream(new FileInputStream(file)));
}
/**
* Process the XML input stream.
*
* @param in XML content only. Any su3 or gunzip handling must have
* already happened.
* @throws IOException on any parse error
*/
public void parse(InputStream in) throws IOException {
_entries = null;
_metadata = null;
JaxpParser parser = new JaxpParser();
try {
Node root = parser.parse(in);
extract(root);
} catch (ParserException pe) {
throw new I2PParserException(pe);
}
}
/**
* The news entries.
* Must call parse() first.
*
* @return sorted, newest first, null if parse failed
*/
public List<NewsEntry> getEntries() {
return _entries;
}
/**
* The news metatdata.
* Must call parse() first.
*
* @return null if parse failed
*/
public NewsMetadata getMetadata() {
return _metadata;
}
private void extract(Node root) throws I2PParserException {
if (!root.getName().equals("feed"))
throw new I2PParserException("no feed in XML");
_metadata = extractNewsMetadata(root);
_entries = extractNewsEntries(root);
}
private static NewsMetadata extractNewsMetadata(Node feed) throws I2PParserException {
NewsMetadata rv = new NewsMetadata();
Node n = feed.getNode("title");
if (n != null)
rv.feedTitle = n.getValue();
n = feed.getNode("subtitle");
if (n != null)
rv.feedSubtitle = n.getValue();
n = feed.getNode("id");
if (n != null)
rv.feedID = n.getValue();
n = feed.getNode("updated");
if (n != null) {
String v = n.getValue();
if (v != null) {
long time = RFC3339Date.parse3339Date(v);
if (time > 0)
rv.feedUpdated = time;
}
}
Node r = feed.getNode("i2p:release");
if (r == null)
throw new I2PParserException("no release data in XML");
// release attributes
String a = r.getAttributeValue("date");
if (a.length() > 0) {
long time = RFC3339Date.parse3339Date(a);
if (time > 0)
rv.date = time;
}
a = r.getAttributeValue("minVersion");
if (a.length() > 0)
rv.minVersion = a;
a = r.getAttributeValue("minJavaVersion");
if (a.length() > 0)
rv.minJavaVersion = a;
// release nodes
n = r.getNode("i2p:version");
if (n != null)
rv.i2pVersion = n.getValue();
List<Node> urls = getNodes(r, "i2p:torrent");
for (Node t : urls) {
// returns "" for none
String href = t.getAttributeValue("href");
if (href.length() > 0) {
String type = t.getAttributeValue("type");
if (type.equals("su2"))
rv.su2Torrent = href;
else if (type.equals("su3"))
rv.su3Torrent = href;
}
}
return rv;
}
private List<NewsEntry> extractNewsEntries(Node feed) throws I2PParserException {
List<NewsEntry> rv = new ArrayList<NewsEntry>();
List<Node> entries = getNodes(feed, "entry");
for (Node entry : entries) {
NewsEntry e = new NewsEntry();
Node n = entry.getNode("title");
if (n != null)
e.title = n.getValue();
n = entry.getNode("link");
if (n != null)
e.link = n.getValue();
n = entry.getNode("id");
if (n != null)
e.id = n.getValue();
n = entry.getNode("updated");
if (n != null) {
String v = n.getValue();
if (v != null) {
long time = RFC3339Date.parse3339Date(v);
if (time > 0)
e.updated = time;
}
}
n = entry.getNode("summary");
if (n != null)
e.summary = n.getValue();
n = entry.getNode("author");
if (n != null) {
n = n.getNode("name");
if (n != null)
e.authorName = n.getValue();
}
n = entry.getNode("content");
if (n != null) {
String a = n.getAttributeValue("type");
if (a.length() > 0)
e.contentType = a;
// now recursively sanitize
// and convert everything in the content to string
StringBuilder buf = new StringBuilder(256);
for (int i = 0; i < n.getNNodes(); i++) {
Node sn = n.getNode(i);
try {
boolean removed = validate(sn);
if (removed) {
i--;
continue;
}
} catch (I2PParserException ipe) {
switch (_mode) {
case ABORT:
throw ipe;
case SKIP_ENTRY:
if (_log.shouldLog(Log.WARN))
_log.warn("Skipping entry", ipe);
e = null;
break;
case REMOVE_ELEMENT:
if (_log.shouldLog(Log.WARN))
_log.warn("Removing element", ipe);
continue;
case ALLOW_ALL:
default:
break;
}
}
if (e == null)
break;
buf.append(sn.toString());
}
if (e == null)
continue;
e.content = buf.toString();
}
rv.add(e);
}
Collections.sort(rv);
return rv;
}
/**
* Helper to get all Nodes matching the name
*/
private static List<Node> getNodes(Node node, String name) {
List<Node> rv = new ArrayList<Node>();
int count = node.getNNodes();
for (int i = 0; i < count; i++) {
Node n = node.getNode(i);
if (n.getName().equals(name))
rv.add(n);
}
return rv;
}
/**
* @throws I2PParserException if any node not in whitelist (depends on mode)
* @return true if node was removed from parent (only for REMOVE_ELEMENT mode)
*/
private boolean validate(Node node) throws I2PParserException {
String name = node.getName();
//if (_log.shouldLog(Log.DEBUG))
// _log.debug("Validating element: " + name);
if (!xhtmlWhitelist.contains(name.toLowerCase(Locale.US))) {
switch (_mode) {
case ABORT:
case SKIP_ENTRY:
throw new I2PParserException("Invalid XHTML element \"" + name + '"');
case REMOVE_ELEMENT:
// fixme this screws up the iteration
if (_log.shouldLog(Log.WARN))
_log.warn("Removing element: " + node);
node.getParentNode().removeNode(node);
return true;
case ALLOW_ALL:
if (_log.shouldLog(Log.WARN))
_log.warn("Allowing non-whitelisted element by configuration: " + node);
break;
}
}
int count = node.getNNodes();
for (int i = 0; i < node.getNNodes(); i++) {
boolean removed = validate(node.getNode(i));
if (removed)
i--;
}
return false;
}
/**
* Extend IOE since cybergarage ParserException extends Exception
*/
private static class I2PParserException extends IOException {
public I2PParserException(String s) {
super(s);
}
public I2PParserException(Throwable t) {
super("XML Parse Error", t);
}
}
public static void main(String[] args) {
try {
NewsXMLParser parser = new NewsXMLParser(new I2PAppContext());
parser.setXHTMLMode(XHTMLMode.ABORT);
//parser.setXHTMLMode(XHTMLMode.REMOVE_ELEMENT);
//parser.setXHTMLMode(XHTMLMode.SKIP_ENTRY);
//parser.setXHTMLMode(XHTMLMode.ALLOW_ALL);
parser.parse(new File(args[0]));
NewsMetadata ud = parser.getMetadata();
List<NewsEntry> entries = parser.getEntries();
System.out.println("Latest version is " + ud.i2pVersion);
System.out.println("Release timestamp: " + ud.date);
System.out.println("Feed timestamp: " + ud.feedUpdated);
System.out.println("Found " + entries.size() + " news entries");
for (int i = 0; i < entries.size(); i++) {
NewsEntry e = entries.get(i);
System.out.println("News #" + (i+1) + ": " + e.title + '\n' + e.content);
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}

View File

@ -0,0 +1,122 @@
package net.i2p.router.news;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import net.i2p.util.SystemVersion;
/**
* Adapted from net.i2p.router.util.RFC822Date.
* This only supports parsing of the dates specified by Atom (RFC 4287)
* and a couple of others.
* In particular, 'T' is required, and either 'Z' or a numeric timezone offset is required,
* unless there's no time fields at all.
*
* The full variety of RFC 3339 (ISO 8601) dates is not supported by the parser,
* but they could be added in the future.
*
* See also: http://stackoverflow.com/questions/6038136/how-do-i-parse-rfc-3339-datetimes-with-java
*
* @since 0.9.17
*/
public abstract class RFC3339Date {
// SimpleDateFormat is not thread-safe, methods must be synchronized
private static final SimpleDateFormat OUTPUT_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
private static final String TZF1, TZF2;
static {
if (SystemVersion.isJava7()) {
// ISO 8601
// These handle timezones like +1000, +10, and +10:00
TZF1 = "yyyy-MM-dd'T'HH:mm:ssXXX";
TZF2 = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
} else {
// These handle timezones like +1000
// These do NOT handle timezones like +10:00
// This is fixed below
TZF1 = "yyyy-MM-dd'T'HH:mm:ssZZZZZ";
TZF2 = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
}
}
/**
* This only supports parsing of the dates specified by Atom, RFC 4287,
* together with the date only.
*/
private static final SimpleDateFormat rfc3339DateFormats[] = new SimpleDateFormat[] {
OUTPUT_FORMAT,
// .S or .SS will get the milliseconds wrong,
// e.g. .1 will become 1 ms, .11 will become 11 ms
// This is NOT fixed below
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US),
new SimpleDateFormat(TZF1, Locale.US),
new SimpleDateFormat(TZF2, Locale.US),
new SimpleDateFormat("yyyy-MM-dd", Locale.US),
// old school for backward compatibility
new SimpleDateFormat("yyyy/MM/dd", Locale.US)
};
//
// The router JVM is forced to UTC but do this just in case
//
static {
TimeZone utc = TimeZone.getTimeZone("GMT");
for (int i = 0; i < rfc3339DateFormats.length; i++) {
rfc3339DateFormats[i].setTimeZone(utc);
}
}
/**
* Parse the date
*
* @param s non-null
* @return -1 on failure
*/
public synchronized static long parse3339Date(String s) {
s = s.trim();
// strip the ':' out of the time zone, if present,
// for Java 6 where we don't have the 'X' format
int len = s.length();
if (!SystemVersion.isJava7() &&
s.charAt(len - 1) != 'Z' &&
s.charAt(len - 3) == ':' &&
(s.charAt(len - 6) == '+' || s.charAt(len - 6) == '-')) {
s = s.substring(0, len - 3) + s.substring(len - 2);
}
for (int i = 0; i < rfc3339DateFormats.length; i++) {
try {
Date date = rfc3339DateFormats[i].parse(s);
if (date != null)
return date.getTime();
} catch (ParseException pe) {}
}
return -1;
}
/**
* Format is "yyyy-MM-ddTHH:mm:ssZ"
*
* @since 0.8.2
*/
public synchronized static String to3339Date(long t) {
return OUTPUT_FORMAT.format(new Date(t));
}
/****
public static void main(String[] args) {
if (args.length == 1) {
try {
System.out.println(to3339Date(Long.parseLong(args[0])));
} catch (NumberFormatException nfe) {
System.out.println(parse3339Date(args[0]));
}
} else {
System.out.println("Usage: RFC3339Date numericDate|stringDate");
}
}
****/
}

View File

@ -0,0 +1,14 @@
<html><body>
<p>
Classes to parse the I2P news format, which follows the Atom
standard with additional metadata for the I2P update notification feature.
</p>
<p>
In the standard router package, this is a router console function, but
these classes are in their own package so they may also
be easily bundled in Android.
</p>
<p>
Since 0.9.17.
</p>
</body></html>

View File

@ -158,8 +158,7 @@ public class ConsoleUpdateManager implements UpdateManager, RouterApp {
// right at instantiation if the news is already indicating a new version
Checker c = new NewsHandler(_context, this);
register(c, NEWS, HTTP, 0);
// TODO
//register(c, NEWS_SU3, HTTP, 0);
register(c, NEWS_SU3, HTTP, 0);
register(c, ROUTER_SIGNED, HTTP, 0); // news is an update checker for the router
Updater u = new UpdateHandler(_context, this);
register(u, ROUTER_SIGNED, HTTP, 0);

View File

@ -1,22 +1,37 @@
package net.i2p.router.update;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import net.i2p.crypto.SU3File;
import net.i2p.crypto.TrustedUpdate;
import net.i2p.data.DataHelper;
import net.i2p.router.RouterContext;
import net.i2p.router.RouterVersion;
import net.i2p.router.news.NewsEntry;
import net.i2p.router.news.NewsMetadata;
import net.i2p.router.news.NewsXMLParser;
import net.i2p.router.util.RFC822Date;
import net.i2p.router.web.ConfigUpdateHandler;
import net.i2p.router.web.NewsHelper;
@ -26,6 +41,8 @@ import static net.i2p.update.UpdateMethod.*;
import net.i2p.util.EepGet;
import net.i2p.util.FileUtil;
import net.i2p.util.Log;
import net.i2p.util.ReusableGZIPInputStream;
import net.i2p.util.SecureFileOutputStream;
import net.i2p.util.SSLEepGet;
import net.i2p.util.VersionComparator;
@ -328,6 +345,10 @@ class NewsFetcher extends UpdateRunner {
long now = _context.clock().now();
if (_tempFile.exists()) {
if (url.endsWith(".su3")) {
processSU3();
return;
}
boolean copied = FileUtil.copy(_tempFile, _newsFile, true, false);
_tempFile.delete();
if (copied) {
@ -351,4 +372,111 @@ class NewsFetcher extends UpdateRunner {
/** override to prevent status update */
@Override
public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) {}
/**
* Process the fetched su3 news file _tempFile
*
* @since 0.9.17
*/
private void processSU3() {
SU3File su3 = new SU3File(_context, _tempFile);
File to1 = new File(_context.getTempDir(), "tmp-" + _context.random().nextInt() + ".xml");
File to2 = new File(_context.getTempDir(), "tmp2-" + _context.random().nextInt() + ".xml");
String sudVersion;
String signingKeyName;
try {
su3.verifyAndMigrate(to1);
int type = su3.getFileType();
if (type != SU3File.TYPE_XML && type != SU3File.TYPE_XML_GZ)
throw new IOException("bad file type");
if (su3.getContentType() != SU3File.CONTENT_NEWS)
throw new IOException("bad content type");
File xml;
if (type == SU3File.TYPE_XML_GZ) {
gunzip(to1, to2);
xml = to2;
} else {
xml = to1;
}
sudVersion = su3.getVersionString();
signingKeyName = su3.getSignerString();
NewsXMLParser parser = new NewsXMLParser(_context);
parser.parse(xml);
NewsMetadata data = parser.getMetadata();
List<NewsEntry> entries = parser.getEntries();
outputOldNewsXML(data, entries);
} catch (IOException ioe) {
// FIXME
//statusDone("<b>" + ioe + ' ' + _("from {0}", _currentURI.toString()) + " </b>");
_tempFile.delete();
to1.delete();
to2.delete();
return;
}
}
/**
* Gunzip the file
*
* @since 0.9.17
*/
private static void gunzip(File from, File to) throws IOException {
ReusableGZIPInputStream in = ReusableGZIPInputStream.acquire();
OutputStream out = null;
try {
in.initialize(new FileInputStream(from));
out = new SecureFileOutputStream(to);
byte buf[] = new byte[4096];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
} finally {
if (out != null) try {
out.close();
} catch (IOException ioe) {}
ReusableGZIPInputStream.release(in);
}
}
/**
* Output in the old format.
* Yes there is a better way.
*
* @since 0.9.17
*/
private void outputOldNewsXML(NewsMetadata data, List<NewsEntry> entries) throws IOException {
Writer out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(_newsFile), "UTF-8"));
out.write("<!--\n");
out.write("<i2p.news date=\"$Date: 2014-09-20 00:00:00 $\">\n");
out.write("<i2p.release ");
if (data.i2pVersion != null)
out.write(" version=\"" + data.i2pVersion + '"');
if (data.minVersion != null)
out.write(" minVersion=\"" + data.minVersion + '"');
if (data.minJavaVersion != null)
out.write(" minJavaVersion=\"" + data.minJavaVersion + '"');
if (data.su2Torrent != null)
out.write(" su2Torrent=\"" + data.su2Torrent + '"');
if (data.su3Torrent != null)
out.write(" su3Torrent=\"" + data.su3Torrent + '"');
out.write("/>\n");
out.write("-->\n");
for (NewsEntry e : entries) {
if (e.title == null || e.content == null)
continue;
out.write("<h3>");
out.write(e.title);
out.write("</h3>\n");
out.write(e.content);
out.write("\n\n");
}
} finally {
if (out != null) try {
out.close();
} catch (IOException ioe) {}
}
}
}

View File

@ -47,13 +47,12 @@ class NewsHandler extends UpdateHandler implements Checker {
return null;
List<URI> updateSources = new ArrayList<URI>(2);
try {
// TODO SU3
// This may be su3 or xml
updateSources.add(new URI(ConfigUpdateHelper.getNewsURL(_context)));
} catch (URISyntaxException use) {}
try {
// TODO
//updateSources.add(new URI(BACKUP_NEWS_URL_SU3));
updateSources.add(new URI(BACKUP_NEWS_URL));
//updateSources.add(new URI(BACKUP_NEWS_URL));
updateSources.add(new URI(BACKUP_NEWS_URL_SU3));
} catch (URISyntaxException use) {}
UpdateRunner update = new NewsFetcher(_context, _mgr, updateSources);
return update;

View File

@ -30,8 +30,11 @@ public class ConfigUpdateHandler extends FormHandler {
public static final String PROP_NEWS_URL = "router.newsURL";
// public static final String DEFAULT_NEWS_URL = "http://dev.i2p.net/cgi-bin/cvsweb.cgi/i2p/news.xml?rev=HEAD";
/** very old default */
public static final String OLD_DEFAULT_NEWS_URL = "http://complication.i2p/news.xml";
/** old default */
public static final String DEFAULT_NEWS_URL = "http://echelon.i2p/i2p/news.xml";
/** current default */
public static final String DEFAULT_NEWS_URL_SU3 = "http://echelon.i2p/i2p/news.su3";
public static final String PROP_REFRESH_FREQUENCY = "router.newsRefreshFrequency";
public static final long DEFAULT_REFRESH_FREQ = 36*60*60*1000l;

View File

@ -32,12 +32,12 @@ public class ConfigUpdateHelper extends HelperBase {
/** hack to replace the old news location with the new one, even if they have saved
the update page at some point */
public static String getNewsURL(I2PAppContext ctx) {
// TODO SU3
String url = ctx.getProperty(ConfigUpdateHandler.PROP_NEWS_URL);
if (url != null && !url.equals(ConfigUpdateHandler.OLD_DEFAULT_NEWS_URL))
if (url != null && !url.equals(ConfigUpdateHandler.OLD_DEFAULT_NEWS_URL) &&
!url.equals(ConfigUpdateHandler.DEFAULT_NEWS_URL))
return url;
else
return ConfigUpdateHandler.DEFAULT_NEWS_URL;
return ConfigUpdateHandler.DEFAULT_NEWS_URL_SU3;
}
public String getUpdateURL() {

View File

@ -197,6 +197,11 @@
<ant dir="apps/routerconsole/java/" target="jar" />
</target>
<!-- newsxml.jar only (subset of routerconsole, no war) for Android -->
<target name="buildNewsXMLJar" depends="buildRouter" >
<ant dir="apps/routerconsole/java/" target="newsxmljar" />
</target>
<target name="buildJetty" depends="buildCore" >
<ant dir="apps/jetty" target="build" />
<copy todir="build/" >
@ -554,7 +559,7 @@
<group title="Core SDK (i2p.jar)" packages="net.i2p:net.i2p.*:net.i2p.client:net.i2p.client.*:net.i2p.internal:net.i2p.internal.*:freenet.support.CPUInformation:org.bouncycastle.oldcrypto:org.bouncycastle.oldcrypto.*:gnu.crypto.*:gnu.getopt:gnu.gettext:com.nettgryppa.security:net.metanotion:net.metanotion.*" />
<group title="Streaming Library" packages="net.i2p.client.streaming:net.i2p.client.streaming.impl" />
<group title="Router" packages="net.i2p.router:net.i2p.router.*:net.i2p.data.i2np:net.i2p.data.router:org.cybergarage.*:org.freenetproject:org.xlattice.crypto.filters" />
<group title="Router Console" packages="net.i2p.router.web:net.i2p.router.update" />
<group title="Router Console" packages="net.i2p.router.web:net.i2p.router.update:net.i2p.router.news" />
<!-- apps and bridges starting here, alphabetical please -->
<group title="Addressbook Application" packages="net.i2p.addressbook" />
<group title="BOB Bridge" packages="net.i2p.BOB" />

View File

@ -71,6 +71,8 @@ public class SU3File {
public static final int TYPE_XML = 1;
/** @since 0.9.15 */
public static final int TYPE_HTML = 2;
/** @since 0.9.17 */
public static final int TYPE_XML_GZ = 3;
public static final int CONTENT_UNKNOWN = 0;
public static final int CONTENT_ROUTER = 1;

View File

@ -25,5 +25,7 @@ public enum UpdateType {
/** @since 0.9.9 */
ROUTER_SIGNED_SU3,
/** @since 0.9.15 */
NEWS_SU3
NEWS_SU3,
/** @since 0.9.17 */
ROUTER_DEV_SU3
}