From addc9c5ca339172618c1089a570c176d3906c645 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 15 Sep 2015 13:33:29 +0000 Subject: [PATCH] News: connect it all together (ticket #1425): - Enable new NewsManager to load/store feed items on disk by UUID - News items are stored forever, not lost when they are removed from feed - News read in once at startup, not at every summary bar refresh - Convert old initialNews.xml and news.xml to NewsEntry format - Limit display to 2 news items in summary bar, /home and /console - New /news page to show all news --- .../src/net/i2p/router/news/NewsManager.java | 49 ++++++++++-- .../net/i2p/router/news/NewsXMLParser.java | 11 ++- .../src/net/i2p/router/news/PersistNews.java | 4 +- .../net/i2p/router/update/NewsFetcher.java | 17 ++++- .../net/i2p/router/web/NewsFeedHelper.java | 8 +- .../src/net/i2p/router/web/NewsHelper.java | 32 ++------ .../i2p/router/web/RouterConsoleRunner.java | 5 +- .../i2p/router/web/SummaryBarRenderer.java | 74 ++++++++++--------- apps/routerconsole/jsp/news.jsp | 3 +- history.txt | 6 ++ .../src/net/i2p/router/RouterVersion.java | 2 +- 11 files changed, 130 insertions(+), 81 deletions(-) diff --git a/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java b/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java index 91290988c..781449ac9 100644 --- a/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java +++ b/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java @@ -4,10 +4,13 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.Reader; +import java.text.DateFormat; +import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.TimeZone; import net.i2p.I2PAppContext; import net.i2p.app.ClientAppManager; @@ -34,7 +37,13 @@ public class NewsManager implements RouterApp { private final ClientAppManager _cmgr; private volatile ClientAppState _state = UNINITIALIZED; private List _currentNews; - private NewsMetadata _currentMetadata; + // TODO + // Metadata is persisted in the old news.xml format by + // NewsFetcher.outputOldNewsXML() and read in at startup by + // ConsoleUpdateManager.startup() and NewsFetcher.checkForUpdates(). + // While running, the UpdateManager keeps the metadata. + // NewsHelper looks at the news.xml timestamp. + //private NewsMetadata _currentMetadata; public static final String APP_NAME = "news"; private static final String BUNDLE_NAME = "net.i2p.router.news.messages"; @@ -93,10 +102,13 @@ public class NewsManager implements RouterApp { String id = e.id; if (id == null) continue; + String title = e.title; boolean found = false; for (int i = 0; i < _currentNews.size(); i++) { NewsEntry old = _currentNews.get(i); - if (id.equals(old.id)) { + // try to prevent dups with those created from old news.xml, + // where the UUID is the title + if (id.equals(old.id) || (title != null && title.equals(old.id))) { _currentNews.set(i, e); found = true; break; @@ -156,7 +168,7 @@ public class NewsManager implements RouterApp { String newsContent = FileUtil.readTextFile(file.toString(), -1, true); if (newsContent == null || newsContent.equals("")) return Collections.emptyList(); - return parseNews(newsContent); + return parseNews(newsContent, false); } private List parseInitialNews() { @@ -171,7 +183,7 @@ public class NewsManager implements RouterApp { while((len = reader.read(buf)) > 0) { out.append(buf, 0, len); } - List rv = parseNews(out.toString()); + List rv = parseNews(out.toString(), true); if (!rv.isEmpty()) { rv.get(0).updated = RFC3339Date.parse3339Date("2015-01-01"); } else { @@ -191,7 +203,12 @@ public class NewsManager implements RouterApp { } } - private List parseNews(String newsContent) { + /** + * Used for initialNews.xml and news.xml + * + * @param addMissingDiv true for initialNews, false for news.xml + */ + private List parseNews(String newsContent, boolean addMissingDiv) { List rv = new ArrayList(); // Parse news content for headings. boolean foundEntry = false; @@ -205,10 +222,28 @@ public class NewsManager implements RouterApp { if (newsContent.length() > start + 16 && newsContent.substring(start + 4, start + 6).equals("20") && newsContent.substring(start + 14, start + 16).equals(": ")) { + // initialNews.xml, or old news.xml from server entry.updated = RFC3339Date.parse3339Date(newsContent.substring(start + 4, start + 14)); newsContent = newsContent.substring(start+16); } else { newsContent = newsContent.substring(start+4); + int colon = newsContent.indexOf(": "); + if (colon > 0 && colon <= 10) { + // Parse the format we wrote it out in, in NewsFetcher.outputOldNewsXML() + // Doesn't work if the date has a : in it, but SHORT hopefully does not + DateFormat fmt = DateFormat.getDateInstance(DateFormat.SHORT); + // the router sets the JVM time zone to UTC but saves the original here so we can get it + String systemTimeZone = _context.getProperty("i2p.systemTimeZone"); + if (systemTimeZone != null) + fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone)); + try { + Date date = fmt.parse(newsContent.substring(0, colon)); + entry.updated = date.getTime(); + newsContent = newsContent.substring(colon + 2); + } catch (ParseException pe) { + // can't find date, will be zero + } + } } int end = newsContent.indexOf(""); if (end >= 0) { @@ -222,6 +257,10 @@ public class NewsManager implements RouterApp { entry.content = newsContent.substring(0, end); else entry.content = newsContent; + // initialNews.xml has the
before the

, not after, so we lose it... + // add it back. + if (addMissingDiv) + entry.content = "
\n" + entry.content; rv.add(entry); start = end; } diff --git a/apps/routerconsole/java/src/net/i2p/router/news/NewsXMLParser.java b/apps/routerconsole/java/src/net/i2p/router/news/NewsXMLParser.java index 8850cb141..20d8746c7 100644 --- a/apps/routerconsole/java/src/net/i2p/router/news/NewsXMLParser.java +++ b/apps/routerconsole/java/src/net/i2p/router/news/NewsXMLParser.java @@ -97,10 +97,11 @@ public class NewsXMLParser { * * @param file XML content only. Any su3 or gunzip handling must have * already happened. + * @return the root node * @throws IOException on any parse error */ - public void parse(File file) throws IOException { - parse(new BufferedInputStream(new FileInputStream(file))); + public Node parse(File file) throws IOException { + return parse(new BufferedInputStream(new FileInputStream(file))); } /** @@ -108,15 +109,17 @@ public class NewsXMLParser { * * @param in XML content only. Any su3 or gunzip handling must have * already happened. + * @return the root node * @throws IOException on any parse error */ - public void parse(InputStream in) throws IOException { + public Node parse(InputStream in) throws IOException { _entries = null; _metadata = null; XMLParser parser = new XMLParser(_context); try { Node root = parser.parse(in); extract(root); + return root; } catch (ParserException pe) { throw new I2PParserException(pe); } @@ -352,7 +355,7 @@ public class NewsXMLParser { * * @return non-null */ - static List getNodes(Node node, String name) { + public static List getNodes(Node node, String name) { List rv = new ArrayList(); int count = node.getNNodes(); for (int i = 0; i < count; i++) { diff --git a/apps/routerconsole/java/src/net/i2p/router/news/PersistNews.java b/apps/routerconsole/java/src/net/i2p/router/news/PersistNews.java index 47fb63e4a..65e17c6bc 100644 --- a/apps/routerconsole/java/src/net/i2p/router/news/PersistNews.java +++ b/apps/routerconsole/java/src/net/i2p/router/news/PersistNews.java @@ -35,7 +35,7 @@ import org.cybergarage.xml.ParserException; */ class PersistNews { - private static final String DIR = "docs/news"; + private static final String DIR = "docs/feed/news"; private static final String PFX = "news-"; private static final String SFX = ".xml.gz"; private static final String XML_START = "\n"; @@ -207,6 +207,8 @@ class PersistNews { } /** + * Unused for now, as we don't have any way to remember it's deleted. + * * @return success */ public static boolean delete(I2PAppContext ctx, NewsEntry entry) { diff --git a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java index 19d878908..44ab24e3c 100644 --- a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java +++ b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java @@ -21,12 +21,14 @@ import java.util.Map; import java.util.StringTokenizer; import java.util.TimeZone; +import net.i2p.app.ClientAppManager; 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.NewsManager; import net.i2p.router.news.NewsMetadata; import net.i2p.router.news.NewsXMLParser; import net.i2p.router.util.RFC822Date; @@ -45,6 +47,8 @@ import net.i2p.util.SSLEepGet; import net.i2p.util.Translate; import net.i2p.util.VersionComparator; +import org.cybergarage.xml.Node; + /** * Task to fetch updates to the news.xml, and to keep * track of whether that has an announcement for a new version. @@ -475,10 +479,21 @@ class NewsFetcher extends UpdateRunner { xml = to1; } NewsXMLParser parser = new NewsXMLParser(_context); - parser.parse(xml); + Node root = parser.parse(xml); xml.delete(); NewsMetadata data = parser.getMetadata(); List entries = parser.getEntries(); + // add entries to the news manager + ClientAppManager cmgr = _context.clientAppManager(); + if (cmgr != null) { + NewsManager nmgr = (NewsManager) cmgr.getRegisteredApp(NewsManager.APP_NAME); + if (nmgr != null) { + nmgr.addEntries(entries); + List nodes = NewsXMLParser.getNodes(root, "entry"); + nmgr.storeEntries(nodes); + } + } + // store entries and metadata in old news.xml format String sudVersion = su3.getVersionString(); String signingKeyName = su3.getSignerString(); File to3 = new File(_context.getTempDir(), "tmp3-" + _context.random().nextInt() + ".xml"); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsFeedHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsFeedHelper.java index a70d0061d..7a9adae76 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NewsFeedHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsFeedHelper.java @@ -20,7 +20,7 @@ import net.i2p.router.news.NewsManager; public class NewsFeedHelper extends HelperBase { private int _start = 0; - private int _limit = 3; + private int _limit = 2; /** * @param limit less than or equal to zero means all @@ -62,16 +62,16 @@ public class NewsFeedHelper extends HelperBase { for (NewsEntry entry : entries) { if (i++ < start) continue; - buf.append("

"); + buf.append("

"); if (entry.updated > 0) { Date date = new Date(entry.updated); buf.append(fmt.format(date)) .append(": "); } buf.append(entry.title) - .append("

\n") + .append("

\n
\n") .append(entry.content) - .append("\n"); + .append("\n
\n"); if (i >= start + max) break; } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java index be3b5685b..5b068f4b1 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsHelper.java @@ -204,37 +204,12 @@ public class NewsHelper extends ContentHelper { return mgr.getStatus(); } - private static final String BUNDLE_NAME = "net.i2p.router.news.messages"; - /** * If we haven't downloaded news yet, use the translated initial news file */ @Override public String getContent() { - File news = new File(_page); - if (!news.exists()) { - _page = (new File(_context.getBaseDir(), "docs/initialNews/initialNews.xml")).getAbsolutePath(); - // don't use super, translate on-the-fly - Reader reader = null; - try { - char[] buf = new char[512]; - StringBuilder out = new StringBuilder(2048); - reader = new TranslateReader(_context, BUNDLE_NAME, new FileInputStream(_page)); - int len; - while((len = reader.read(buf)) > 0) { - out.append(buf, 0, len); - } - return out.toString(); - } catch (IOException ioe) { - return ""; - } finally { - try { - if (reader != null) - reader.close(); - } catch (IOException foo) {} - } - } - return super.getContent(); + return NewsFeedHelper.getEntries(_context, 0, 2); } /** @@ -312,7 +287,10 @@ public class NewsHelper extends ContentHelper { buf.append(" ") .append(Messages.getString("Show news", ctx)); } - buf.append(""); + buf.append("" + + " - ") + .append(Messages.getString("Show all news", ctx)) + .append(""); } return buf.toString(); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java index 0c36391dd..35e96b6ea 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/RouterConsoleRunner.java @@ -26,8 +26,9 @@ import net.i2p.crypto.KeyStoreUtil; import net.i2p.data.DataHelper; import net.i2p.jetty.I2PLogger; import net.i2p.router.RouterContext; -import net.i2p.router.update.ConsoleUpdateManager; import net.i2p.router.app.RouterApp; +import net.i2p.router.news.NewsManager; +import net.i2p.router.update.ConsoleUpdateManager; import net.i2p.util.Addresses; import net.i2p.util.FileUtil; import net.i2p.util.I2PAppThread; @@ -706,6 +707,8 @@ public class RouterConsoleRunner implements RouterApp { ConsoleUpdateManager um = new ConsoleUpdateManager(_context, _mgr, null); um.start(); + NewsManager nm = new NewsManager(_context, _mgr, null); + nm.startup(); if (PluginStarter.pluginsEnabled(_context)) { t = new I2PAppThread(new PluginStarter(_context), "PluginStarter", true); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java index 1dd724e95..9f79dbfc7 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryBarRenderer.java @@ -3,14 +3,20 @@ package net.i2p.router.web; import java.io.File; import java.io.IOException; import java.io.Writer; +import java.text.DateFormat; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TimeZone; +import net.i2p.app.ClientAppManager; import net.i2p.crypto.SigType; import net.i2p.data.DataHelper; import net.i2p.router.RouterContext; +import net.i2p.router.news.NewsEntry; +import net.i2p.router.news.NewsManager; import net.i2p.util.PortMapper; /** @@ -604,49 +610,45 @@ public class SummaryBarRenderer { String consoleNonce = CSSHelper.getNonce(); if (consoleNonce != null) { // Set up title and pre-headings stuff. - buf.append("

") + //buf.append("

") + buf.append("

") .append(_("News & Updates")) .append("


\n"); // Get news content. - String newsContent = newshelper.getContent(); - if (newsContent != "") { + List entries = Collections.emptyList(); + ClientAppManager cmgr = _context.clientAppManager(); + if (cmgr != null) { + NewsManager nmgr = (NewsManager) cmgr.getRegisteredApp(NewsManager.APP_NAME); + if (nmgr != null) + entries = nmgr.getEntries(); + } + if (!entries.isEmpty()) { buf.append("
    \n"); - // Parse news content for headings. - boolean foundEntry = false; - int start = newsContent.indexOf("

    "); - while (start >= 0) { - // Add offset to start: - // 4 - gets rid of

    - // 16 - gets rid of the date as well (assuming form "

    yyyy-mm-dd: Foobarbaz...") - // Don't truncate the "congratulations" in initial news - if (newsContent.length() > start + 16 && - newsContent.substring(start + 4, start + 6).equals("20") && - newsContent.substring(start + 14, start + 16).equals(": ")) - newsContent = newsContent.substring(start+16, newsContent.length()); - else - newsContent = newsContent.substring(start+4, newsContent.length()); - int end = newsContent.indexOf("

    "); - if (end >= 0) { - String heading = newsContent.substring(0, end); - buf.append("
  • ") - .append(heading) - .append("
  • \n"); - foundEntry = true; + DateFormat fmt = DateFormat.getDateInstance(DateFormat.SHORT); + // the router sets the JVM time zone to UTC but saves the original here so we can get it + String systemTimeZone = _context.getProperty("i2p.systemTimeZone"); + if (systemTimeZone != null) + fmt.setTimeZone(TimeZone.getTimeZone(systemTimeZone)); + int i = 0; + final int max = 2; + for (NewsEntry entry : entries) { + buf.append("
  • "); + if (entry.updated > 0) { + Date date = new Date(entry.updated); + buf.append(fmt.format(date)) + .append(": "); } - start = newsContent.indexOf("

    "); + buf.append(entry.title) + .append("

  • \n"); + if (++i >= max) + break; } buf.append("
\n"); - // Set up string containing to show news. - String requestURI = _helper.getRequestURI(); - if (requestURI.contains("/home") && !foundEntry) { - buf.append("") - .append(_("Show news")) - .append("\n"); - } + //buf.append("") + // .append(_("Show all news")) + // .append("\n"); } else { buf.append("
") .append(_("none")) diff --git a/apps/routerconsole/jsp/news.jsp b/apps/routerconsole/jsp/news.jsp index 7629083bd..422c8b48f 100644 --- a/apps/routerconsole/jsp/news.jsp +++ b/apps/routerconsole/jsp/news.jsp @@ -14,5 +14,6 @@ " /> <% feedHelper.setLimit(0); %> +
-
+

diff --git a/history.txt b/history.txt index 94a5475e4..5b8cecb7d 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,9 @@ +2015-09-15 zzz + * Console: + - Store news feed items separately on disk in XML, like a real feed reader + - Limit display to 2 news items in summary bar, /home and /console + - New /news page to show all news (ticket #1425) + * 2015-09-12 0.9.22 released 2015-09-11 kytv diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 87e5bffca..282c18b42 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 1; + public final static long BUILD = 2; /** for example "-test" */ public final static String EXTRA = "";