From 7912d7650daaa44d42206575f8c90b165b263ac1 Mon Sep 17 00:00:00 2001 From: zzz <zzz@mail.i2p> Date: Mon, 14 Sep 2015 14:49:20 +0000 Subject: [PATCH] News: new NewsManager to maintain current news entries. WIP, not yet hooked in. --- .../src/net/i2p/router/news/NewsManager.java | 242 ++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java diff --git a/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java b/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java new file mode 100644 index 0000000000..5fb8cb492b --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/news/NewsManager.java @@ -0,0 +1,242 @@ +package net.i2p.router.news; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import net.i2p.I2PAppContext; +import net.i2p.app.ClientAppManager; +import net.i2p.app.ClientAppState; +import static net.i2p.app.ClientAppState.*; +import net.i2p.router.app.RouterApp; +import net.i2p.util.FileUtil; +import net.i2p.util.Log; +import net.i2p.util.TranslateReader; + +import org.cybergarage.xml.Node; + +/** + * Manage current news. + * Keeps current entries in memory, and provide methods to + * add new entries and store them to disk. + * + * @since 0.9.23 + */ +public class NewsManager implements RouterApp { + + private final I2PAppContext _context; + private final Log _log; + private final ClientAppManager _cmgr; + private volatile ClientAppState _state = UNINITIALIZED; + private List<NewsEntry> _currentNews; + private NewsMetadata _currentMetadata; + + public static final String APP_NAME = "news"; + private static final String BUNDLE_NAME = "net.i2p.router.news.messages"; + + /** + * @param args ignored + */ + public NewsManager(I2PAppContext ctx, ClientAppManager listener, String[] args) { + _context = ctx; + _cmgr = listener; + _log = ctx.logManager().getLog(NewsManager.class); + _state = INITIALIZED; + } + + /** + * + * @return non-null, sorted by updated date, newest first + */ + public synchronized List<NewsEntry> getEntries() { + if (!_currentNews.isEmpty()) + return new ArrayList(_currentNews); + // load old news.xml + List<NewsEntry> rv = parseOldNews(); + if (!rv.isEmpty()) { + _currentNews = rv; + // don't save to disk as we don't have the UUIDs so they will be dups ?? + return rv; + } + // load and translate initialnews + // We don't save it to _currentNews, as the language may change + return parseInitialNews(); + } + + /** + * Store each entry. + * Old entries are always overwritten, as they may change even without the updated date changing. + * Does NOT update the NewsEntry list. + * + * @param entries each one should be "entry" at the root + * @return success + */ + public synchronized boolean storeEntries(List<Node> entries) { + return PersistNews.store(_context, entries); + } + + /** + * Add or replace each entry in the list. + * Does NOT store them to disk. + */ + public synchronized void addEntries(List<NewsEntry> entries) { + for (NewsEntry e : entries) { + String id = e.id; + if (id == null) + continue; + boolean found = false; + for (int i = 0; i < _currentNews.size(); i++) { + NewsEntry old = _currentNews.get(i); + if (id.equals(old.id)) { + _currentNews.set(i, e); + found = true; + break; + } + } + if (!found) + _currentNews.add(e); + } + Collections.sort(_currentNews); + } + + /////// ClientApp methods + + /** + * ClientApp interface + */ + public synchronized void startup() { + changeState(STARTING); + _currentNews = PersistNews.load(_context); + changeState(RUNNING); + if (_cmgr != null) + _cmgr.register(this); + } + + /** + * ClientApp interface + * @param args ignored + */ + public synchronized void shutdown(String[] args) { + changeState(STOPPED); + } + + public ClientAppState getState() { + return _state; + } + + public String getName() { + return APP_NAME; + } + + public String getDisplayName() { + return "News Manager"; + } + + /////// end ClientApp methods + + private synchronized void changeState(ClientAppState state) { + _state = state; + if (_cmgr != null) + _cmgr.notify(this, state, null, null); + } + + private List<NewsEntry> parseOldNews() { + File file = new File(_context.getConfigDir(), "docs/news.xml"); + String newsContent = FileUtil.readTextFile(file.toString(), -1, true); + if (newsContent == null || newsContent.equals("")) + return Collections.emptyList(); + return parseNews(newsContent); + } + + private List<NewsEntry> parseInitialNews() { + NewsEntry entry = new NewsEntry(); + File file = new File(_context.getBaseDir(), "docs/initialNews/initialNews.xml"); + Reader reader = null; + try { + char[] buf = new char[512]; + StringBuilder out = new StringBuilder(2048); + reader = new TranslateReader(_context, BUNDLE_NAME, new FileInputStream(file)); + int len; + while((len = reader.read(buf)) > 0) { + out.append(buf, 0, len); + } + List<NewsEntry> rv = parseNews(out.toString()); + if (!rv.isEmpty()) { + rv.get(0).updated = RFC3339Date.parse3339Date("2015-01-01"); + } else { + if (_log.shouldWarn()) + _log.warn("failed to load " + file); + } + return rv; + } catch (IOException ioe) { + if (_log.shouldWarn()) + _log.warn("failed to load " + file, ioe); + return Collections.emptyList(); + } finally { + try { + if (reader != null) + reader.close(); + } catch (IOException foo) {} + } + } + + private List<NewsEntry> parseNews(String newsContent) { + List<NewsEntry> rv = new ArrayList<NewsEntry>(); + // Parse news content for headings. + boolean foundEntry = false; + int start = newsContent.indexOf("<h3>"); + while (start >= 0) { + NewsEntry entry = new NewsEntry(); + // Add offset to start: + // 4 - gets rid of <h3> + // 16 - gets rid of the date as well (assuming form "<h3>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(": ")) { + entry.updated = RFC3339Date.parse3339Date(newsContent.substring(start + 4, start + 14)); + newsContent = newsContent.substring(start+16); + } else { + newsContent = newsContent.substring(start+4); + } + int end = newsContent.indexOf("</h3>"); + if (end >= 0) { + String heading = newsContent.substring(0, end); + entry.title = heading; + // use title as UUID + entry.id = heading; + newsContent = newsContent.substring(end + 5); + end = newsContent.indexOf("<h3>"); + if (end > 0) + entry.content = newsContent.substring(0, end); + else + entry.content = newsContent; + rv.add(entry); + start = end; + } + } + Collections.sort(rv); + return rv; + } + + public static void main(String[] args) { + if (args.length != 0) { + System.err.println("Usage: NewsManager"); + System.exit(1); + } + I2PAppContext ctx = new I2PAppContext(); + NewsManager mgr = new NewsManager(ctx, null, null); + mgr.startup(); + List<NewsEntry> entries = mgr.getEntries(); + System.out.println("Loaded " + entries.size() + " news entries"); + for (int i = 0; i < entries.size(); i++) { + NewsEntry e = entries.get(i); + System.out.println("\n****** News #" + (i+1) + ": " + e.title + ' ' + new Date(e.updated) + '\n' + e.content); + } + } +} -- GitLab