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