diff --git a/apps/syndie/java/src/net/i2p/syndie/Archive.java b/apps/syndie/java/src/net/i2p/syndie/Archive.java index 391a1e30ce8982bdc9e3d2d93cbaac27871f634d..7d080ab86df9dce9aaa85f540219ace444fb8979 100644 --- a/apps/syndie/java/src/net/i2p/syndie/Archive.java +++ b/apps/syndie/java/src/net/i2p/syndie/Archive.java @@ -30,6 +30,7 @@ public class Archive { private Map _blogInfo; private ArchiveIndex _index; private EntryExtractor _extractor; + private String _defaultSelector; public static final String METADATA_FILE = "meta.snm"; public static final String INDEX_FILE = "archive.txt"; @@ -50,6 +51,8 @@ public class Archive { _blogInfo = new HashMap(); _index = null; _extractor = new EntryExtractor(ctx); + _defaultSelector = ctx.getProperty("syndie.defaultSelector"); + if (_defaultSelector == null) _defaultSelector = ""; reloadInfo(); } @@ -63,7 +66,12 @@ public class Archive { BlogInfo bi = new BlogInfo(); try { bi.load(new FileInputStream(meta)); - info.add(bi); + if (bi.verify(_context)) { + info.add(bi); + } else { + System.err.println("Invalid blog (but we're storing it anyway): " + bi); + info.add(bi); + } } catch (IOException ioe) { ioe.printStackTrace(); } @@ -79,8 +87,11 @@ public class Archive { } } } + + public String getDefaultSelector() { return _defaultSelector; } public BlogInfo getBlogInfo(BlogURI uri) { + if (uri == null) return null; synchronized (_blogInfo) { return (BlogInfo)_blogInfo.get(uri.getKeyHash()); } @@ -90,14 +101,20 @@ public class Archive { return (BlogInfo)_blogInfo.get(key); } } - public void storeBlogInfo(BlogInfo info) { + public boolean storeBlogInfo(BlogInfo info) { if (!info.verify(_context)) { System.err.println("Not storing the invalid blog " + info); - return; + return false; } + boolean isNew = true; synchronized (_blogInfo) { - _blogInfo.put(info.getKey().calculateHash(), info); + BlogInfo old = (BlogInfo)_blogInfo.get(info.getKey().calculateHash()); + if ( (old == null) || (old.getEdition() < info.getEdition()) ) + _blogInfo.put(info.getKey().calculateHash(), info); + else + isNew = false; } + if (!isNew) return true; // valid entry, but not stored, since its old try { File blogDir = new File(_rootDir, info.getKey().calculateHash().toBase64()); blogDir.mkdirs(); @@ -106,8 +123,10 @@ public class Archive { info.write(out); out.close(); System.out.println("Blog info written to " + blogFile.getPath()); + return true; } catch (IOException ioe) { ioe.printStackTrace(); + return false; } } @@ -262,7 +281,16 @@ public class Archive { } public boolean storeEntry(EntryContainer container) { + if (container == null) return false; BlogURI uri = container.getURI(); + if (uri == null) return false; + + File blogDir = new File(_rootDir, uri.getKeyHash().toBase64()); + blogDir.mkdirs(); + File entryFile = new File(blogDir, getEntryFilename(uri.getEntryId())); + if (entryFile.exists()) return true; + + BlogInfo info = getBlogInfo(uri); if (info == null) { System.out.println("no blog metadata for the uri " + uri); @@ -274,13 +302,10 @@ public class Archive { } else { //System.out.println("Signature is valid: " + container.getSignature() + " for info " + info); } - ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); container.write(baos, true); - File blogDir = new File(_rootDir, uri.getKeyHash().toBase64()); - blogDir.mkdirs(); byte data[] = baos.toByteArray(); - File entryFile = new File(blogDir, getEntryFilename(uri.getEntryId())); FileOutputStream out = new FileOutputStream(entryFile); out.write(data); out.close(); diff --git a/apps/syndie/java/src/net/i2p/syndie/BlogManager.java b/apps/syndie/java/src/net/i2p/syndie/BlogManager.java index 31cc7a28c760786543fa68ad28b98773ab6bc819..b78a23712efda02e7446655174b3cafc9eb385c8 100644 --- a/apps/syndie/java/src/net/i2p/syndie/BlogManager.java +++ b/apps/syndie/java/src/net/i2p/syndie/BlogManager.java @@ -18,6 +18,7 @@ public class BlogManager { private File _archiveDir; private File _userDir; private File _cacheDir; + private File _tempDir; private Archive _archive; static { @@ -42,11 +43,13 @@ public class BlogManager { _archiveDir = new File(root, "archive"); _userDir = new File(root, "users"); _cacheDir = new File(root, "cache"); + _tempDir = new File(root, "temp"); _blogKeyDir.mkdirs(); _privKeyDir.mkdirs(); _archiveDir.mkdirs(); _cacheDir.mkdirs(); _userDir.mkdirs(); + _tempDir.mkdirs(); _archive = new Archive(ctx, _archiveDir.getAbsolutePath(), _cacheDir.getAbsolutePath()); _archive.regenerateIndex(); } @@ -93,6 +96,7 @@ public class BlogManager { } public Archive getArchive() { return _archive; } + public File getTempDir() { return _tempDir; } public List listMyBlogs() { File files[] = _privKeyDir.listFiles(); @@ -175,45 +179,7 @@ public class BlogManager { FileWriter out = null; try { out = new FileWriter(userFile); - out.write("password=" + user.getHashedPassword() + "\n"); - out.write("blog=" + user.getBlog().toBase64() + "\n"); - out.write("lastid=" + user.getMostRecentEntry() + "\n"); - out.write("lastmetaedition=" + user.getLastMetaEntry() + "\n"); - out.write("lastlogin=" + user.getLastLogin() + "\n"); - out.write("addressbook=" + user.getAddressbookLocation() + "\n"); - out.write("showimages=" + user.getShowImages() + "\n"); - out.write("showexpanded=" + user.getShowExpanded() + "\n"); - StringBuffer buf = new StringBuffer(); - buf.append("groups="); - Map groups = user.getBlogGroups(); - for (Iterator iter = groups.keySet().iterator(); iter.hasNext(); ) { - String name = (String)iter.next(); - List selectors = (List)groups.get(name); - buf.append(name).append(':'); - for (int i = 0; i < selectors.size(); i++) { - buf.append(selectors.get(i)); - if (i + 1 < selectors.size()) - buf.append(","); - } - if (iter.hasNext()) - buf.append(' '); - } - buf.append('\n'); - out.write(buf.toString()); - // shitlist=hash,hash,hash - List shitlistedBlogs = user.getShitlistedBlogs(); - if (shitlistedBlogs.size() > 0) { - buf.setLength(0); - buf.append("shitlistedblogs="); - for (int i = 0; i < shitlistedBlogs.size(); i++) { - Hash blog = (Hash)shitlistedBlogs.get(i); - buf.append(blog.toBase64()); - if (i + 1 < shitlistedBlogs.size()) - buf.append(','); - } - buf.append('\n'); - out.write(buf.toString()); - } + out.write(user.export()); } catch (IOException ioe) { ioe.printStackTrace(); } finally { @@ -347,6 +313,39 @@ public class BlogManager { } } + /** + * read in the syndie blog metadata file from the stream, verifying it and adding it to + * the archive if necessary + * + */ + public boolean importBlogMetadata(InputStream metadataStream) throws IOException { + try { + BlogInfo info = new BlogInfo(); + info.load(metadataStream); + return _archive.storeBlogInfo(info); + } catch (IOException ioe) { + ioe.printStackTrace(); + return false; + } + } + + /** + * read in the syndie entry file from the stream, verifying it and adding it to + * the archive if necessary + * + */ + public boolean importBlogEntry(InputStream entryStream) throws IOException { + try { + EntryContainer c = new EntryContainer(); + c.load(entryStream); + return _archive.storeEntry(c); + } catch (IOException ioe) { + ioe.printStackTrace(); + return false; + } + } + + public String addAddress(User user, String name, String location, String schema) { if (!user.getAuthenticated()) return "Not logged in"; boolean ok = validateAddressName(name); diff --git a/apps/syndie/java/src/net/i2p/syndie/User.java b/apps/syndie/java/src/net/i2p/syndie/User.java index 37d031fd4e23a17ff0348fd553d804a5ea6b61c8..736dd6bdfa0a1f30974663ab92ad7983fbc21920 100644 --- a/apps/syndie/java/src/net/i2p/syndie/User.java +++ b/apps/syndie/java/src/net/i2p/syndie/User.java @@ -24,9 +24,17 @@ public class User { private String _addressbookLocation; private boolean _showImagesByDefault; private boolean _showExpandedByDefault; + private String _defaultSelector; private long _lastLogin; private long _lastMetaEntry; + private boolean _allowAccessRemote; private boolean _authenticated; + private String _eepProxyHost; + private int _eepProxyPort; + private String _webProxyHost; + private int _webProxyPort; + private String _torProxyHost; + private int _torProxyPort; public User() { _context = I2PAppContext.getGlobalContext(); @@ -40,9 +48,17 @@ public class User { _mostRecentEntry = -1; _blogGroups = new HashMap(); _shitlistedBlogs = new ArrayList(); + _defaultSelector = null; _addressbookLocation = "userhosts.txt"; _showImagesByDefault = false; _showExpandedByDefault = false; + _allowAccessRemote = false; + _eepProxyHost = null; + _webProxyHost = null; + _torProxyHost = null; + _eepProxyPort = -1; + _webProxyPort = -1; + _torProxyPort = -1; _lastLogin = -1; _lastMetaEntry = 0; } @@ -60,10 +76,21 @@ public class User { public long getLastLogin() { return _lastLogin; } public String getHashedPassword() { return _hashedPassword; } public long getLastMetaEntry() { return _lastMetaEntry; } + public String getDefaultSelector() { return _defaultSelector; } + public void setDefaultSelector(String sel) { _defaultSelector = sel; } + public boolean getAllowAccessRemote() { return _allowAccessRemote; } + public void setAllowAccessRemote(boolean allow) { _allowAccessRemote = true; } public void setMostRecentEntry(long id) { _mostRecentEntry = id; } public void setLastMetaEntry(long id) { _lastMetaEntry = id; } + public String getEepProxyHost() { return _eepProxyHost; } + public int getEepProxyPort() { return _eepProxyPort; } + public String getWebProxyHost() { return _webProxyHost; } + public int getWebProxyPort() { return _webProxyPort; } + public String getTorProxyHost() { return _torProxyHost; } + public int getTorProxyPort() { return _torProxyPort; } + public void invalidate() { BlogManager.instance().saveUser(this); init(); @@ -135,11 +162,75 @@ public class User { _showImagesByDefault = (show != null) && (show.equals("true")); show = props.getProperty("showexpanded", "false"); _showExpandedByDefault = (show != null) && (show.equals("true")); - + _defaultSelector = props.getProperty("defaultselector"); + String allow = props.getProperty("allowaccessremote", "false"); + _allowAccessRemote = (allow != null) && (allow.equals("true")); + _eepProxyPort = getInt(props.getProperty("eepproxyport")); + _webProxyPort = getInt(props.getProperty("webproxyport")); + _torProxyPort = getInt(props.getProperty("torproxyport")); + _eepProxyHost = props.getProperty("eepproxyhost"); + _webProxyHost = props.getProperty("webproxyhost"); + _torProxyHost = props.getProperty("torproxyhost"); _lastLogin = _context.clock().now(); _authenticated = true; return LOGIN_OK; } + private int getInt(String val) { + if (val == null) return -1; + try { return Integer.parseInt(val); } catch (NumberFormatException nfe) { return -1; } + } + public static final String LOGIN_OK = "Logged in"; + + public String export() { + StringBuffer buf = new StringBuffer(512); + buf.append("password=" + getHashedPassword() + "\n"); + buf.append("blog=" + getBlog().toBase64() + "\n"); + buf.append("lastid=" + getMostRecentEntry() + "\n"); + buf.append("lastmetaedition=" + getLastMetaEntry() + "\n"); + buf.append("lastlogin=" + getLastLogin() + "\n"); + buf.append("addressbook=" + getAddressbookLocation() + "\n"); + buf.append("showimages=" + getShowImages() + "\n"); + buf.append("showexpanded=" + getShowExpanded() + "\n"); + buf.append("defaultselector=" + getDefaultSelector() + "\n"); + buf.append("allowaccessremote=" + _allowAccessRemote + "\n"); + buf.append("eepproxyhost="+_eepProxyHost+"\n"); + buf.append("eepproxyport="+_eepProxyPort+"\n"); + buf.append("webproxyhost="+_webProxyHost+"\n"); + buf.append("webproxyport="+_webProxyPort+"\n"); + buf.append("torproxyhost="+_torProxyHost+"\n"); + buf.append("torproxyport="+_torProxyPort+"\n"); + + buf.append("groups="); + Map groups = getBlogGroups(); + for (Iterator iter = groups.keySet().iterator(); iter.hasNext(); ) { + String name = (String)iter.next(); + List selectors = (List)groups.get(name); + buf.append(name).append(':'); + for (int i = 0; i < selectors.size(); i++) { + buf.append(selectors.get(i)); + if (i + 1 < selectors.size()) + buf.append(","); + } + if (iter.hasNext()) + buf.append(' '); + } + buf.append('\n'); + // shitlist=hash,hash,hash + List shitlistedBlogs = getShitlistedBlogs(); + if (shitlistedBlogs.size() > 0) { + buf.setLength(0); + buf.append("shitlistedblogs="); + for (int i = 0; i < shitlistedBlogs.size(); i++) { + Hash blog = (Hash)shitlistedBlogs.get(i); + buf.append(blog.toBase64()); + if (i + 1 < shitlistedBlogs.size()) + buf.append(','); + } + buf.append('\n'); + } + + return buf.toString(); + } } diff --git a/apps/syndie/java/src/net/i2p/syndie/data/ArchiveIndex.java b/apps/syndie/java/src/net/i2p/syndie/data/ArchiveIndex.java index cf45436eed364af5d5093e7f602d4873605394ae..819fe3e8666f31d054fb57179c042186b088c527 100644 --- a/apps/syndie/java/src/net/i2p/syndie/data/ArchiveIndex.java +++ b/apps/syndie/java/src/net/i2p/syndie/data/ArchiveIndex.java @@ -77,6 +77,44 @@ public class ArchiveIndex { /** get the raw entry size (including attachments) from the given blog/tag pair */ public long getBlogEntrySizeKB(int index, int entryIndex) { return ((EntrySummary)((BlogSummary)_blogs.get(index)).entries.get(entryIndex)).size; } + public boolean getEntryIsKnown(BlogURI uri) { return getEntry(uri) != null; } + public long getBlogEntrySizeKB(BlogURI uri) { + EntrySummary entry = getEntry(uri); + if (entry == null) return -1; + return entry.size; + } + private EntrySummary getEntry(BlogURI uri) { + if ( (uri == null) || (uri.getKeyHash() == null) || (uri.getEntryId() < 0) ) return null; + for (int i = 0; i < _blogs.size(); i++) { + BlogSummary summary = (BlogSummary)_blogs.get(i); + if (summary.blog.equals(uri.getKeyHash())) { + for (int j = 0; j < summary.entries.size(); j++) { + EntrySummary entry = (EntrySummary)summary.entries.get(j); + if (entry.entry.equals(uri)) + return entry; + } + } + } + return null; + } + public Set getBlogEntryTags(BlogURI uri) { + Set tags = new HashSet(); + if ( (uri == null) || (uri.getKeyHash() == null) || (uri.getEntryId() < 0) ) return tags; + for (int i = 0; i < _blogs.size(); i++) { + BlogSummary summary = (BlogSummary)_blogs.get(i); + if (summary.blog.equals(uri.getKeyHash())) { + for (int j = 0; j < summary.entries.size(); j++) { + EntrySummary entry = (EntrySummary)summary.entries.get(j); + if (entry.entry.equals(uri)) { + tags.add(summary.tag); + break; + } + } + } + } + return tags; + } + /** how many 'new' blogs are listed */ public int getNewestBlogCount() { return _newestBlogs.size(); } public Hash getNewestBlog(int index) { return (Hash)_newestBlogs.get(index); } diff --git a/apps/syndie/java/src/net/i2p/syndie/data/BlogInfo.java b/apps/syndie/java/src/net/i2p/syndie/data/BlogInfo.java index b6d491cf8b53999c37d43b996096e4b763cabcd1..3ac0ecca5140db485c3b8d29fa946f1c9e00a186 100644 --- a/apps/syndie/java/src/net/i2p/syndie/data/BlogInfo.java +++ b/apps/syndie/java/src/net/i2p/syndie/data/BlogInfo.java @@ -13,6 +13,7 @@ import net.i2p.I2PAppContext; * Required keys: * Owner: base64 of their signing public key * Signature: base64 of the DSA signature of the rest of the ordered metadata + * Edition: base10 unique identifier for this metadata (higher clobbers lower) * * Optional keys: * Posters: comma delimited list of base64 signing public keys that @@ -53,6 +54,7 @@ public class BlogInfo { public static final String SIGNATURE = "Signature"; public static final String NAME = "Name"; public static final String DESCRIPTION = "Description"; + public static final String EDITION = "Edition"; public void load(InputStream in) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(in)); @@ -63,8 +65,13 @@ public class BlogInfo { line = line.trim(); int len = line.length(); int split = line.indexOf(':'); - if ( (len <= 0) || (split <= 0) || (split >= len - 2) ) + if ( (len <= 0) || (split <= 0) ) { continue; + } else if (split >= len - 1) { + names.add(line.substring(0, split).trim()); + vals.add(""); + continue; + } String key = line.substring(0, split).trim(); String val = line.substring(split+1).trim(); @@ -102,7 +109,8 @@ public class BlogInfo { if ( (includeRealSignature) || (!SIGNATURE.equals(_optionNames[i])) ) buf.append(_optionNames[i]).append(':').append(_optionValues[i]).append('\n'); } - out.write(buf.toString().getBytes()); + String s = buf.toString(); + out.write(s.getBytes()); } public String getProperty(String name) { @@ -133,6 +141,18 @@ public class BlogInfo { _optionValues = values; } + public int getEdition() { + String e = getProperty(EDITION); + if (e != null) { + try { + return Integer.parseInt(e); + } catch (NumberFormatException nfe) { + return 0; + } + } + return 0; + } + public String[] getProperties() { return _optionNames; } public SigningPublicKey[] getPosters() { return _posters; } @@ -151,7 +171,9 @@ public class BlogInfo { try { ByteArrayOutputStream out = new ByteArrayOutputStream(512); write(out, false); - return ctx.dsa().verifySignature(_signature, out.toByteArray(), _key); + out.close(); + byte data[] = out.toByteArray(); + return ctx.dsa().verifySignature(_signature, data, _key); } catch (IOException ioe) { return false; } @@ -192,4 +214,52 @@ public class BlogInfo { } return buf.toString(); } + + public static void main(String args[]) { + I2PAppContext ctx = I2PAppContext.getGlobalContext(); + /* + try { + Object keys[] = ctx.keyGenerator().generateSigningKeypair(); + SigningPublicKey pub = (SigningPublicKey)keys[0]; + SigningPrivateKey priv = (SigningPrivateKey)keys[1]; + + Properties opts = new Properties(); + opts.setProperty("Name", "n4m3"); + opts.setProperty("Description", "foo"); + opts.setProperty("Edition", "0"); + opts.setProperty("ContactURL", "u@h.org"); + + BlogInfo info = new BlogInfo(pub, null, opts); + System.err.println("\n"); + System.err.println("\n"); + info.sign(ctx, priv); + System.err.println("\n"); + boolean ok = info.verify(ctx); + System.err.println("\n"); + System.err.println("sign&verify: " + ok); + System.err.println("\n"); + System.err.println("\n"); + + FileOutputStream o = new FileOutputStream("bloginfo-test.dat"); + info.write(o, true); + o.close(); + FileInputStream i = new FileInputStream("bloginfo-test.dat"); + byte buf[] = new byte[4096]; + int sz = DataHelper.read(i, buf); + BlogInfo read = new BlogInfo(); + read.load(new ByteArrayInputStream(buf, 0, sz)); + ok = read.verify(ctx); + System.err.println("write to disk, verify read: " + ok); + System.err.println("Data: " + Base64.encode(buf, 0, sz)); + System.err.println("Str : " + new String(buf, 0, sz)); + } catch (Exception e) { e.printStackTrace(); } + */ + try { + FileInputStream in = new FileInputStream(args[0]); + BlogInfo info = new BlogInfo(); + info.load(in); + boolean ok = info.verify(I2PAppContext.getGlobalContext()); + System.out.println("OK? " + ok + " :" + info); + } catch (Exception e) { e.printStackTrace(); } + } } diff --git a/apps/syndie/java/src/net/i2p/syndie/data/EntryContainer.java b/apps/syndie/java/src/net/i2p/syndie/data/EntryContainer.java index 6afc3fa389f4cbb007afeea1ab4a7313822a0e5a..4aaa76dd8819ce85276b3988a0e8f24520cf7d03 100644 --- a/apps/syndie/java/src/net/i2p/syndie/data/EntryContainer.java +++ b/apps/syndie/java/src/net/i2p/syndie/data/EntryContainer.java @@ -72,7 +72,9 @@ public class EntryContainer { public int getFormat() { return _format; } public void load(InputStream source) throws IOException { - String fmt = DataHelper.readLine(source).trim(); + String line = DataHelper.readLine(source); + if (line == null) throw new IOException("No format line in the entry"); + String fmt = line.trim(); if (FORMAT_ZIP_UNENCRYPTED_STR.equals(fmt)) { _format = FORMAT_ZIP_UNENCRYPTED; } else if (FORMAT_ZIP_ENCRYPTED_STR.equals(fmt)) { @@ -81,7 +83,6 @@ public class EntryContainer { throw new IOException("Unsupported entry format: " + fmt); } - String line = null; while ( (line = DataHelper.readLine(source)) != null) { line = line.trim(); int len = line.length(); @@ -99,17 +100,24 @@ public class EntryContainer { parseHeaders(); String sigStr = DataHelper.readLine(source); + if ( (sigStr == null) || (sigStr.indexOf("Signature:") == -1) ) + throw new IOException("No signature line"); sigStr = sigStr.substring("Signature:".length()+1).trim(); _signature = new Signature(Base64.decode(sigStr)); //System.out.println("Sig: " + _signature.toBase64()); - line = DataHelper.readLine(source).trim(); + line = DataHelper.readLine(source); + if (line == null) + throw new IOException("No size line"); + line = line.trim(); int dataSize = -1; try { int index = line.indexOf("Size:"); if (index == 0) dataSize = Integer.parseInt(line.substring("Size:".length()+1).trim()); + else + throw new IOException("Invalid size line"); } catch (NumberFormatException nfe) { throw new IOException("Invalid entry size: " + line); } diff --git a/apps/syndie/java/src/net/i2p/syndie/sml/HTMLPreviewRenderer.java b/apps/syndie/java/src/net/i2p/syndie/sml/HTMLPreviewRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..64d8d3bab5cd508b8680b99d1531ec29fa1e0d58 --- /dev/null +++ b/apps/syndie/java/src/net/i2p/syndie/sml/HTMLPreviewRenderer.java @@ -0,0 +1,109 @@ +package net.i2p.syndie.sml; + +import java.io.*; +import java.text.*; +import java.util.*; +import net.i2p.data.*; +import net.i2p.syndie.*; +import net.i2p.syndie.data.*; +import net.i2p.syndie.web.*; + +/** + * + */ +public class HTMLPreviewRenderer extends HTMLRenderer { + private List _filenames; + private List _fileTypes; + private List _files; + + public HTMLPreviewRenderer(List filenames, List fileTypes, List files) { + super(); + _filenames = filenames; + _fileTypes = fileTypes; + _files = files; + } + + protected String getAttachmentURLBase() { return "viewtempattachment.jsp"; } + protected String getAttachmentURL(int id) { + return getAttachmentURLBase() + "?" + + ArchiveViewerBean.PARAM_ATTACHMENT + "=" + id; + } + + public void receiveAttachment(int id, String anchorText) { + if (!continueBody()) { return; } + if ( (id < 0) || (_files == null) || (id >= _files.size()) ) { + _bodyBuffer.append(sanitizeString(anchorText)); + } else { + File f = (File)_files.get(id); + String name = (String)_filenames.get(id); + String type = (String)_fileTypes.get(id); + _bodyBuffer.append("<a href=\"").append(getAttachmentURL(id)).append("\">"); + _bodyBuffer.append(sanitizeString(anchorText)).append("</a>"); + _bodyBuffer.append(" (").append(f.length()/1024).append("KB, "); + _bodyBuffer.append(" \"").append(sanitizeString(name)).append("\", "); + _bodyBuffer.append(sanitizeString(type)).append(")"); + } + } + + public void receiveEnd() { + _postBodyBuffer.append("</td></tr>\n"); + _postBodyBuffer.append("<tr>\n"); + _postBodyBuffer.append("<form action=\"").append(getAttachmentURLBase()).append("\">\n"); + _postBodyBuffer.append("<td colspan=\"2\" valign=\"top\" align=\"left\" class=\"syndieEntryAttachmentsCell\"\n"); + + if (_files.size() > 0) { + _postBodyBuffer.append("<b>Attachments:</b> "); + _postBodyBuffer.append("<select name=\"").append(ArchiveViewerBean.PARAM_ATTACHMENT).append("\">\n"); + for (int i = 0; i < _files.size(); i++) { + _postBodyBuffer.append("<option value=\"").append(i).append("\">"); + File f = (File)_files.get(i); + String name = (String)_filenames.get(i); + String type = (String)_fileTypes.get(i); + _postBodyBuffer.append(sanitizeString(name)); + _postBodyBuffer.append(" (").append(f.length()/1024).append("KB"); + _postBodyBuffer.append(", type ").append(sanitizeString(type)).append(")</option>\n"); + } + _postBodyBuffer.append("</select>\n"); + _postBodyBuffer.append("<input type=\"submit\" value=\"Download\" name=\"Download\" /><br />\n"); + } + + if (_blogs.size() > 0) { + _postBodyBuffer.append("<b>Blog references:</b> "); + for (int i = 0; i < _blogs.size(); i++) { + Blog b = (Blog)_blogs.get(i); + _postBodyBuffer.append("<a href=\"").append(getPageURL(new Hash(Base64.decode(b.hash)), b.tag, b.entryId, -1, -1, (_user != null ? _user.getShowExpanded() : false), (_user != null ? _user.getShowImages() : false))); + _postBodyBuffer.append("\">").append(sanitizeString(b.name)).append("</a> "); + } + _postBodyBuffer.append("<br />\n"); + } + + if (_links.size() > 0) { + _postBodyBuffer.append("<b>External links:</b> "); + for (int i = 0; i < _links.size(); i++) { + Link l = (Link)_links.get(i); + _postBodyBuffer.append("<a href=\"externallink.jsp?schema="); + _postBodyBuffer.append(sanitizeURL(l.schema)).append("&location="); + _postBodyBuffer.append(sanitizeURL(l.location)); + _postBodyBuffer.append("\">").append(sanitizeString(l.location)); + _postBodyBuffer.append(" (").append(sanitizeString(l.schema)).append(")</a> "); + } + _postBodyBuffer.append("<br />\n"); + } + + if (_addresses.size() > 0) { + _postBodyBuffer.append("<b>Addresses:</b> "); + for (int i = 0; i < _addresses.size(); i++) { + Address a = (Address)_addresses.get(i); + _postBodyBuffer.append("<a href=\"addaddress.jsp?schema="); + _postBodyBuffer.append(sanitizeURL(a.schema)).append("&location="); + _postBodyBuffer.append(sanitizeURL(a.location)).append("&name="); + _postBodyBuffer.append(sanitizeURL(a.name)); + _postBodyBuffer.append("\">").append(sanitizeString(a.name)); + } + _postBodyBuffer.append("<br />\n"); + } + + _postBodyBuffer.append("</td>\n</form>\n</tr>\n"); + _postBodyBuffer.append("</table>\n"); + } +} diff --git a/apps/syndie/java/src/net/i2p/syndie/sml/HTMLRenderer.java b/apps/syndie/java/src/net/i2p/syndie/sml/HTMLRenderer.java index 06580fe1c7e97d27c61187d3751d2c0356bedb4a..622b2a4e60bf81419af0b7f8a0c9a41201951f08 100644 --- a/apps/syndie/java/src/net/i2p/syndie/sml/HTMLRenderer.java +++ b/apps/syndie/java/src/net/i2p/syndie/sml/HTMLRenderer.java @@ -12,23 +12,23 @@ import net.i2p.syndie.web.*; * */ public class HTMLRenderer extends EventReceiverImpl { - private SMLParser _parser; - private Writer _out; - private User _user; - private Archive _archive; - private EntryContainer _entry; - private boolean _showImages; - private boolean _cutBody; - private boolean _cutReached; - private int _cutSize; - private int _lastNewlineAt; - private Map _headers; - private List _addresses; - private List _links; - private List _blogs; - private StringBuffer _preBodyBuffer; - private StringBuffer _bodyBuffer; - private StringBuffer _postBodyBuffer; + protected SMLParser _parser; + protected Writer _out; + protected User _user; + protected Archive _archive; + protected EntryContainer _entry; + protected boolean _showImages; + protected boolean _cutBody; + protected boolean _cutReached; + protected int _cutSize; + protected int _lastNewlineAt; + protected Map _headers; + protected List _addresses; + protected List _links; + protected List _blogs; + protected StringBuffer _preBodyBuffer; + protected StringBuffer _bodyBuffer; + protected StringBuffer _postBodyBuffer; public HTMLRenderer() { _parser = new SMLParser(); @@ -190,7 +190,7 @@ public class HTMLRenderer extends EventReceiverImpl { } /** are we either before the cut or rendering without cutting? */ - private boolean continueBody() { + protected boolean continueBody() { boolean rv = ( (!_cutReached) && (_bodyBuffer.length() <= _cutSize) ) || (!_cutBody); //if (!rv) // System.out.println("rv: " + rv + " Cut reached: " + _cutReached + " bodyBufferSize: " + _bodyBuffer.length() + " cutBody? " + _cutBody); @@ -227,7 +227,7 @@ public class HTMLRenderer extends EventReceiverImpl { _bodyBuffer.append(']'); } - private static class Blog { + protected static class Blog { public String name; public String hash; public String tag; @@ -317,7 +317,7 @@ public class HTMLRenderer extends EventReceiverImpl { _bodyBuffer.append("] "); } - private static class Link { + protected static class Link { public String schema; public String location; public int hashCode() { return -1; } @@ -340,7 +340,7 @@ public class HTMLRenderer extends EventReceiverImpl { _bodyBuffer.append(sanitizeURL(text)).append("\">").append(sanitizeString(text)).append("</a>"); } - private static class Address { + protected static class Address { public String name; public String schema; public String location; @@ -381,79 +381,113 @@ public class HTMLRenderer extends EventReceiverImpl { public void receiveEnd() { _postBodyBuffer.append("</td></tr>\n"); - _postBodyBuffer.append("<tr>\n"); - _postBodyBuffer.append("<form action=\"").append(getAttachmentURLBase()).append("\">\n"); - _postBodyBuffer.append("<input type=\"hidden\" name=\"").append(ArchiveViewerBean.PARAM_BLOG); - _postBodyBuffer.append("\" value=\""); - if (_entry != null) - _postBodyBuffer.append(Base64.encode(_entry.getURI().getKeyHash().getData())); - else - _postBodyBuffer.append("unknown"); - _postBodyBuffer.append("\" />\n"); - _postBodyBuffer.append("<input type=\"hidden\" name=\"").append(ArchiveViewerBean.PARAM_ENTRY); - _postBodyBuffer.append("\" value=\""); - if (_entry != null) - _postBodyBuffer.append(_entry.getURI().getEntryId()); - else - _postBodyBuffer.append("unknown"); - _postBodyBuffer.append("\" />\n"); - _postBodyBuffer.append("<td valign=\"top\" align=\"left\" style=\"entry.attachments.cell\" bgcolor=\"#77ff77\">\n"); - - if ( (_entry != null) && (_entry.getAttachments() != null) && (_entry.getAttachments().length > 0) ) { - _postBodyBuffer.append("<b>Attachments:</b> "); - _postBodyBuffer.append("<select name=\"").append(ArchiveViewerBean.PARAM_ATTACHMENT).append("\">\n"); - for (int i = 0; i < _entry.getAttachments().length; i++) { - _postBodyBuffer.append("<option value=\"").append(i).append("\">"); - Attachment a = _entry.getAttachments()[i]; - _postBodyBuffer.append(sanitizeString(a.getName())); - if ( (a.getDescription() != null) && (a.getDescription().trim().length() > 0) ) { - _postBodyBuffer.append(": "); - _postBodyBuffer.append(sanitizeString(a.getDescription())); + if (_cutBody) { + _postBodyBuffer.append("<tr class=\"syndieEntryAttachmentsCell\">\n"); + _postBodyBuffer.append("<td colspan=\"2\" valign=\"top\" align=\"left\" class=\"syndieEntryAttachmentsCell\">"); + _postBodyBuffer.append("<a href=\"").append(getEntryURL()).append("\">View details...</a> "); + + if ( (_entry != null) && (_entry.getAttachments() != null) && (_entry.getAttachments().length > 0) ) { + int num = _entry.getAttachments().length; + if (num == 1) + _postBodyBuffer.append("1 attachment "); + else + _postBodyBuffer.append(num + " attachments "); + } + + int blogs = _blogs.size(); + if (blogs == 1) + _postBodyBuffer.append("1 blog reference "); + else if (blogs > 1) + _postBodyBuffer.append(blogs).append(" blog references "); + + int links = _links.size(); + if (links == 1) + _postBodyBuffer.append("1 external link "); + else if (links > 1) + _postBodyBuffer.append(links).append(" external links"); + + int addrs = _addresses.size(); + if (addrs == 1) + _postBodyBuffer.append("1 address "); + else if (addrs > 1) + _postBodyBuffer.append(addrs).append(" addresses "); + + _postBodyBuffer.append("</td></tr>\n"); + } else { + _postBodyBuffer.append("<tr class=\"syndieEntryAttachmentsCell\">\n"); + _postBodyBuffer.append("<form action=\"").append(getAttachmentURLBase()).append("\">\n"); + _postBodyBuffer.append("<input type=\"hidden\" name=\"").append(ArchiveViewerBean.PARAM_BLOG); + _postBodyBuffer.append("\" value=\""); + if (_entry != null) + _postBodyBuffer.append(Base64.encode(_entry.getURI().getKeyHash().getData())); + else + _postBodyBuffer.append("unknown"); + _postBodyBuffer.append("\" />\n"); + _postBodyBuffer.append("<input type=\"hidden\" name=\"").append(ArchiveViewerBean.PARAM_ENTRY); + _postBodyBuffer.append("\" value=\""); + if (_entry != null) + _postBodyBuffer.append(_entry.getURI().getEntryId()); + else + _postBodyBuffer.append("unknown"); + _postBodyBuffer.append("\" />\n"); + _postBodyBuffer.append("<td colspan=\"2\" valign=\"top\" align=\"left\" class=\"syndieEntryAttachmentsCell\">\n"); + + if ( (_entry != null) && (_entry.getAttachments() != null) && (_entry.getAttachments().length > 0) ) { + _postBodyBuffer.append("<b>Attachments:</b> "); + _postBodyBuffer.append("<select name=\"").append(ArchiveViewerBean.PARAM_ATTACHMENT).append("\">\n"); + for (int i = 0; i < _entry.getAttachments().length; i++) { + _postBodyBuffer.append("<option value=\"").append(i).append("\">"); + Attachment a = _entry.getAttachments()[i]; + _postBodyBuffer.append(sanitizeString(a.getName())); + if ( (a.getDescription() != null) && (a.getDescription().trim().length() > 0) ) { + _postBodyBuffer.append(": "); + _postBodyBuffer.append(sanitizeString(a.getDescription())); + } + _postBodyBuffer.append(" (").append(a.getDataLength()/1024).append("KB"); + _postBodyBuffer.append(", type ").append(sanitizeString(a.getMimeType())).append(")</option>\n"); } - _postBodyBuffer.append(" (").append(a.getDataLength()/1024).append("KB"); - _postBodyBuffer.append(", type ").append(sanitizeString(a.getMimeType())).append(")</option>\n"); + _postBodyBuffer.append("</select>\n"); + _postBodyBuffer.append("<input type=\"submit\" value=\"Download\" name=\"Download\" /><br />\n"); } - _postBodyBuffer.append("</select>\n"); - _postBodyBuffer.append("<input type=\"submit\" value=\"Download\" name=\"Download\" /><br />\n"); - } - - if (_blogs.size() > 0) { - _postBodyBuffer.append("<b>Blog references:</b> "); - for (int i = 0; i < _blogs.size(); i++) { - Blog b = (Blog)_blogs.get(i); - _postBodyBuffer.append("<a href=\"").append(getPageURL(new Hash(Base64.decode(b.hash)), b.tag, b.entryId, -1, -1, (_user != null ? _user.getShowExpanded() : false), (_user != null ? _user.getShowImages() : false))); - _postBodyBuffer.append("\">").append(sanitizeString(b.name)).append("</a> "); + + if (_blogs.size() > 0) { + _postBodyBuffer.append("<b>Blog references:</b> "); + for (int i = 0; i < _blogs.size(); i++) { + Blog b = (Blog)_blogs.get(i); + _postBodyBuffer.append("<a href=\"").append(getPageURL(new Hash(Base64.decode(b.hash)), b.tag, b.entryId, -1, -1, (_user != null ? _user.getShowExpanded() : false), (_user != null ? _user.getShowImages() : false))); + _postBodyBuffer.append("\">").append(sanitizeString(b.name)).append("</a> "); + } + _postBodyBuffer.append("<br />\n"); } - _postBodyBuffer.append("<br />\n"); - } - - if (_links.size() > 0) { - _postBodyBuffer.append("<b>External links:</b> "); - for (int i = 0; i < _links.size(); i++) { - Link l = (Link)_links.get(i); - _postBodyBuffer.append("<a href=\"externallink.jsp?schema="); - _postBodyBuffer.append(sanitizeURL(l.schema)).append("&location="); - _postBodyBuffer.append(sanitizeURL(l.location)); - _postBodyBuffer.append("\">").append(sanitizeString(l.location)); - _postBodyBuffer.append(" (").append(sanitizeString(l.schema)).append(")</a> "); + + if (_links.size() > 0) { + _postBodyBuffer.append("<b>External links:</b> "); + for (int i = 0; i < _links.size(); i++) { + Link l = (Link)_links.get(i); + _postBodyBuffer.append("<a href=\"externallink.jsp?schema="); + _postBodyBuffer.append(sanitizeURL(l.schema)).append("&location="); + _postBodyBuffer.append(sanitizeURL(l.location)); + _postBodyBuffer.append("\">").append(sanitizeString(l.location)); + _postBodyBuffer.append(" (").append(sanitizeString(l.schema)).append(")</a> "); + } + _postBodyBuffer.append("<br />\n"); } - _postBodyBuffer.append("<br />\n"); - } - - if (_addresses.size() > 0) { - _postBodyBuffer.append("<b>Addresses:</b> "); - for (int i = 0; i < _addresses.size(); i++) { - Address a = (Address)_addresses.get(i); - _postBodyBuffer.append("<a href=\"addaddress.jsp?schema="); - _postBodyBuffer.append(sanitizeURL(a.schema)).append("&location="); - _postBodyBuffer.append(sanitizeURL(a.location)).append("&name="); - _postBodyBuffer.append(sanitizeURL(a.name)); - _postBodyBuffer.append("\">").append(sanitizeString(a.name)); + + if (_addresses.size() > 0) { + _postBodyBuffer.append("<b>Addresses:</b> "); + for (int i = 0; i < _addresses.size(); i++) { + Address a = (Address)_addresses.get(i); + _postBodyBuffer.append("<a href=\"addaddress.jsp?schema="); + _postBodyBuffer.append(sanitizeURL(a.schema)).append("&location="); + _postBodyBuffer.append(sanitizeURL(a.location)).append("&name="); + _postBodyBuffer.append(sanitizeURL(a.name)); + _postBodyBuffer.append("\">").append(sanitizeString(a.name)); + } + _postBodyBuffer.append("<br />\n"); } - _postBodyBuffer.append("<br />\n"); + + _postBodyBuffer.append("</td>\n</form>\n</tr>\n"); } - - _postBodyBuffer.append("</td>\n</form>\n</tr>\n"); _postBodyBuffer.append("</table>\n"); } @@ -463,8 +497,9 @@ public class HTMLRenderer extends EventReceiverImpl { } public void receiveHeaderEnd() { - renderMetaCell(); + _preBodyBuffer.append("<table width=\"100%\" border=\"0\">\n"); renderSubjectCell(); + renderMetaCell(); renderPreBodyCell(); } @@ -473,25 +508,24 @@ public class HTMLRenderer extends EventReceiverImpl { public static final String HEADER_IN_REPLY_TO = "InReplyTo"; private void renderSubjectCell() { - _preBodyBuffer.append("<td align=\"left\" valign=\"top\" style=\"entry.subject.cell\" bgcolor=\"#3355ff\">"); + _preBodyBuffer.append("<tr class=\"syndieEntrySubjectCell\"><td align=\"left\" valign=\"top\" class=\"syndieEntrySubjectCell\" width=\"400\"> "); String subject = (String)_headers.get(HEADER_SUBJECT); if (subject == null) subject = "[no subject]"; _preBodyBuffer.append(sanitizeString(subject)); - _preBodyBuffer.append("</td></tr>\n"); + _preBodyBuffer.append("</td>\n"); } private void renderPreBodyCell() { String bgcolor = (String)_headers.get(HEADER_BGCOLOR); if (_cutBody) - _preBodyBuffer.append("<tr><td align=\"left\" valign=\"top\" style=\"entry.summary.cell\" bgcolor=\"" + (bgcolor == null ? "#33ffff" : sanitizeTagParam(bgcolor)) + "\">"); + _preBodyBuffer.append("<tr class=\"syndieEntrySummaryCell\"><td colspan=\"2\" align=\"left\" valign=\"top\" class=\"syndieEntrySummaryCell\" " + (bgcolor != null ? "bgcolor=\"" + sanitizeTagParam(bgcolor) + "\"" : "") + "\">"); else - _preBodyBuffer.append("<tr><td align=\"left\" valign=\"top\" style=\"entry.body.cell\" bgcolor=\"" + (bgcolor == null ? "#33ffff" : sanitizeTagParam(bgcolor)) + "\">"); + _preBodyBuffer.append("<tr class=\"syndieEntryBodyCell\"><td colspan=\"2\" align=\"left\" valign=\"top\" class=\"syndieEntryBodyCell\" " + (bgcolor != null ? "bgcolor=\"" + sanitizeTagParam(bgcolor) + "\"" : "") + "\">"); } private void renderMetaCell() { - _preBodyBuffer.append("<table width=\"100%\" border=\"0\">\n"); - _preBodyBuffer.append("<tr><td align=\"left\" valign=\"top\" rowspan=\"3\" style=\"entry.meta.cell\" bgcolor=\"#33ccff\">\n"); + _preBodyBuffer.append("<td nowrap=\"true\" align=\"right\" valign=\"top\" class=\"syndieEntryMetaCell\">\n"); BlogInfo info = null; if (_entry != null) info = _archive.getBlogInfo(_entry.getURI()); @@ -506,31 +540,32 @@ public class HTMLRenderer extends EventReceiverImpl { } else { _preBodyBuffer.append("[unknown blog]"); } - _preBodyBuffer.append("<br />\n"); String tags[] = (_entry != null ? _entry.getTags() : null); - _preBodyBuffer.append("<i>"); - for (int i = 0; tags != null && i < tags.length; i++) { - _preBodyBuffer.append("<a href=\""); - _preBodyBuffer.append(getPageURL(_entry.getURI().getKeyHash(), tags[i], -1, -1, -1, (_user != null ? _user.getShowExpanded() : false), (_user != null ? _user.getShowImages() : false))); - _preBodyBuffer.append("\">"); - _preBodyBuffer.append(sanitizeString(tags[i])); - _preBodyBuffer.append("</a>"); - if (i + 1 < tags.length) - _preBodyBuffer.append(", "); + if ( (tags != null) && (tags.length > 0) ) { + _preBodyBuffer.append(" Tags: "); + _preBodyBuffer.append("<i>"); + for (int i = 0; tags != null && i < tags.length; i++) { + _preBodyBuffer.append("<a href=\""); + _preBodyBuffer.append(getPageURL(_entry.getURI().getKeyHash(), tags[i], -1, -1, -1, (_user != null ? _user.getShowExpanded() : false), (_user != null ? _user.getShowImages() : false))); + _preBodyBuffer.append("\">"); + _preBodyBuffer.append(sanitizeString(tags[i])); + _preBodyBuffer.append("</a>"); + if (i + 1 < tags.length) + _preBodyBuffer.append(", "); + } + _preBodyBuffer.append("</i>"); } - _preBodyBuffer.append("</i><br /><font size=\"-1\">\n"); + _preBodyBuffer.append(" "); if (_entry != null) _preBodyBuffer.append(getEntryDate(_entry.getURI().getEntryId())); else _preBodyBuffer.append(getEntryDate(new Date().getTime())); - _preBodyBuffer.append("</font><br />"); String inReplyTo = (String)_headers.get(HEADER_IN_REPLY_TO); - System.err.println("In reply to: [" + inReplyTo + "]"); if ( (inReplyTo != null) && (inReplyTo.trim().length() > 0) ) - _preBodyBuffer.append("<a href=\"").append(getPageURL(sanitizeTagParam(inReplyTo))).append("\">In reply to</a><br />\n"); + _preBodyBuffer.append(" <a href=\"").append(getPageURL(sanitizeTagParam(inReplyTo))).append("\">In reply to</a>\n"); if ( (_user != null) && (_user.getAuthenticated()) ) - _preBodyBuffer.append("<a href=\"").append(getPostURL(_user.getBlog(), true)).append("\">Reply</a><br />\n"); - _preBodyBuffer.append("\n</td>\n"); + _preBodyBuffer.append(" <a href=\"").append(getPostURL(_user.getBlog(), true)).append("\">Reply</a>\n"); + _preBodyBuffer.append("\n</td></tr>\n"); } private final SimpleDateFormat _dateFormat = new SimpleDateFormat("yyyy/MM/dd"); @@ -539,7 +574,7 @@ public class HTMLRenderer extends EventReceiverImpl { try { String str = _dateFormat.format(new Date(when)); long dayBegin = _dateFormat.parse(str).getTime(); - return str + "<br />" + (when - dayBegin); + return str + "." + (when - dayBegin); } catch (ParseException pe) { pe.printStackTrace(); // wtf @@ -548,12 +583,26 @@ public class HTMLRenderer extends EventReceiverImpl { } } - public static final String sanitizeString(String str) { + public static final String sanitizeString(String str) { return sanitizeString(str, true); } + public static final String sanitizeString(String str, boolean allowNL) { if (str == null) return null; - if ( (str.indexOf('<') < 0) && (str.indexOf('>') < 0) ) - return str; + boolean unsafe = false; + unsafe = unsafe || str.indexOf('<') >= 0; + unsafe = unsafe || str.indexOf('>') >= 0; + if (!allowNL) { + unsafe = unsafe || str.indexOf('\n') >= 0; + unsafe = unsafe || str.indexOf('\r') >= 0; + unsafe = unsafe || str.indexOf('\f') >= 0; + } + if (!unsafe) return str; + str = str.replace('<', '_'); str = str.replace('>', '-'); + if (!allowNL) { + str = str.replace('\n', ' '); + str = str.replace('\r', ' '); + str = str.replace('\f', ' '); + } return str; } @@ -575,8 +624,8 @@ public class HTMLRenderer extends EventReceiverImpl { "&" + ArchiveViewerBean.PARAM_EXPAND_ENTRIES + "=true"; } - private String getAttachmentURLBase() { return "viewattachment.jsp"; } - private String getAttachmentURL(int id) { + protected String getAttachmentURLBase() { return "viewattachment.jsp"; } + protected String getAttachmentURL(int id) { if (_entry == null) return "unknown"; return getAttachmentURLBase() + "?" + ArchiveViewerBean.PARAM_BLOG + "=" + diff --git a/apps/syndie/java/src/net/i2p/syndie/web/ArchiveViewerBean.java b/apps/syndie/java/src/net/i2p/syndie/web/ArchiveViewerBean.java index 8c15f867f0f4c357a44a3bda28e6136b9410e074..b9d6a62b63f055cc0f32e5d29049a2a27732ed42 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/ArchiveViewerBean.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/ArchiveViewerBean.java @@ -79,12 +79,26 @@ public class ArchiveViewerBean { public static final String SEL_BLOGTAG = "blogtag://"; public static final String SEL_ENTRY = "entry://"; public static final String SEL_GROUP = "group://"; + /** submit field for the selector form */ + public static final String PARAM_SELECTOR_ACTION = "action"; + public static final String SEL_ACTION_SET_AS_DEFAULT = "Set as default"; public static void renderBlogSelector(User user, Map parameters, Writer out) throws IOException { + String sel = getString(parameters, PARAM_SELECTOR); + String action = getString(parameters, PARAM_SELECTOR_ACTION); + if ( (sel != null) && (action != null) && (SEL_ACTION_SET_AS_DEFAULT.equals(action)) ) { + user.setDefaultSelector(HTMLRenderer.sanitizeString(sel, false)); + BlogManager.instance().saveUser(user); + } + out.write("<select name=\""); out.write(PARAM_SELECTOR); out.write("\">"); out.write("<option value=\""); + out.write(getDefaultSelector(user, parameters)); + out.write("\">Default blog filter</option>\n"); + out.write("\">"); + out.write("<option value=\""); out.write(SEL_ALL); out.write("\">All posts from all blogs</option>\n"); @@ -157,6 +171,13 @@ public class ArchiveViewerBean { } + private static String getDefaultSelector(User user, Map parameters) { + if ( (user == null) || (user.getDefaultSelector() == null) ) + return BlogManager.instance().getArchive().getDefaultSelector(); + else + return user.getDefaultSelector(); + } + public static void renderBlogs(User user, Map parameters, Writer out) throws IOException { String blogStr = getString(parameters, PARAM_BLOG); Hash blog = null; @@ -174,6 +195,8 @@ public class ArchiveViewerBean { if (group != null) group = new String(Base64.decode(group)); String sel = getString(parameters, PARAM_SELECTOR); + if ( (sel == null) && (blog == null) && (group == null) && (tag == null) ) + sel = getDefaultSelector(user, parameters); if (sel != null) { Selector s = new Selector(sel); blog = s.blog; @@ -349,7 +372,7 @@ public class ArchiveViewerBean { return rv; } - private static final String getString(Map parameters, String param) { + public static final String getString(Map parameters, String param) { if ( (parameters == null) || (parameters.get(param) == null) ) return null; Object vals = parameters.get(param); @@ -369,6 +392,24 @@ public class ArchiveViewerBean { return null; } } + public static final String[] getStrings(Map parameters, String param) { + if ( (parameters == null) || (parameters.get(param) == null) ) + return null; + Object vals = parameters.get(param); + if (vals.getClass().isArray()) { + return (String[])vals; + } else if (vals instanceof Collection) { + Collection c = (Collection)vals; + if (c.size() <= 0) return null; + String rv[] = new String[c.size()]; + int i = 0; + for (Iterator iter = c.iterator(); iter.hasNext(); i++) + rv[i] = (String)iter.next(); + return rv; + } else { + return null; + } + } private static final int getInt(Map param, String key, int defaultVal) { String val = getString(param, key); diff --git a/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java b/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java new file mode 100644 index 0000000000000000000000000000000000000000..59e257d0b76d39b6ac43fc7e06bf97ab3161218c --- /dev/null +++ b/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java @@ -0,0 +1,136 @@ +package net.i2p.syndie.web; + +import java.io.*; +import java.util.*; +import net.i2p.syndie.*; +import net.i2p.syndie.data.BlogURI; +import net.i2p.syndie.sml.HTMLPreviewRenderer; + +/** + * + */ +public class PostBean { + private User _user; + private String _subject; + private String _tags; + private String _headers; + private String _text; + private List _filenames; + private List _fileStreams; + private List _localFiles; + private List _fileTypes; + private boolean _previewed; + + public PostBean() { reinitialize(); } + + public void reinitialize() { + System.out.println("Reinitializing " + (_text != null ? "(with " + _text.length() + " bytes of sml!)" : "")); + _user = null; + _subject = null; + _tags = null; + _text = null; + _headers = null; + _filenames = new ArrayList(); + _fileStreams = new ArrayList(); + _fileTypes = new ArrayList(); + if (_localFiles != null) + for (int i = 0; i < _localFiles.size(); i++) + ((File)_localFiles.get(i)).delete(); + + _localFiles = new ArrayList(); + _previewed = false; + } + + public User getUser() { return _user; } + public String getSubject() { return (_subject != null ? _subject : ""); } + public String getTags() { return (_tags != null ? _tags : ""); } + public String getText() { return (_text != null ? _text : ""); } + public String getHeaders() { return (_headers != null ? _headers : ""); } + public void setUser(User user) { _user = user; } + public void setSubject(String subject) { _subject = subject; } + public void setTags(String tags) { _tags = tags; } + public void setText(String text) { _text = text; } + public void setHeaders(String headers) { _headers = headers; } + + public String getContentType(int id) { + if ( (id >= 0) && (id < _fileTypes.size()) ) + return (String)_fileTypes.get(id); + return "application/octet-stream"; + } + + public void writeAttachmentData(int id, OutputStream out) throws IOException { + FileInputStream in = new FileInputStream((File)_localFiles.get(id)); + byte buf[] = new byte[1024]; + int read = 0; + while ( (read = in.read(buf)) != -1) + out.write(buf, 0, read); + out.close(); + } + + public void addAttachment(String filename, InputStream fileStream, String mimeType) { + _filenames.add(filename); + _fileStreams.add(fileStream); + _fileTypes.add(mimeType); + } + public int getAttachmentCount() { return (_filenames != null ? _filenames.size() : 0); } + + public BlogURI postEntry() throws IOException { + if (!_previewed) return null; + List localStreams = new ArrayList(_localFiles.size()); + for (int i = 0; i < _localFiles.size(); i++) { + File f = (File)_localFiles.get(i); + localStreams.add(new FileInputStream(f)); + } + return BlogManager.instance().createBlogEntry(_user, _subject, _tags, _headers, _text, + _filenames, localStreams, _fileTypes); + } + + public void renderPreview(Writer out) throws IOException { + System.out.println("Subject: " + _subject); + System.out.println("Text: " + _text); + System.out.println("Headers: " + _headers); + // cache all the _fileStreams into temporary files, storing those files in _localFiles + // then render the page accordingly with an HTMLRenderer, altered to use a different + // 'view attachment' + cacheAttachments(); + String smlContent = renderSMLContent(); + HTMLPreviewRenderer r = new HTMLPreviewRenderer(_filenames, _fileTypes, _localFiles); + r.render(_user, BlogManager.instance().getArchive(), null, smlContent, out, false, true); + _previewed = true; + } + + private String renderSMLContent() { + StringBuffer raw = new StringBuffer(); + raw.append("Subject: ").append(_subject).append('\n'); + raw.append("Tags: "); + StringTokenizer tok = new StringTokenizer(_tags, " \t\n"); + while (tok.hasMoreTokens()) + raw.append(tok.nextToken()).append('\t'); + raw.append('\n'); + raw.append(_headers.trim()); + raw.append("\n\n"); + raw.append(_text.trim()); + return raw.toString(); + } + + private void cacheAttachments() throws IOException { + File postCacheDir = new File(BlogManager.instance().getTempDir(), _user.getBlog().toBase64()); + if (!postCacheDir.exists()) + postCacheDir.mkdirs(); + for (int i = 0; i < _fileStreams.size(); i++) { + InputStream in = (InputStream)_fileStreams.get(i); + File f = File.createTempFile("attachment", ".dat", postCacheDir); + FileOutputStream o = new FileOutputStream(f); + byte buf[] = new byte[1024]; + int read = 0; + while ( (read = in.read(buf)) != -1) + o.write(buf, 0, read); + o.close(); + in.close(); + _localFiles.add(f); + System.out.println("Caching attachment " + i + " temporarily in " + + f.getAbsolutePath() + " w/ " + f.length() + "bytes"); + } + _fileStreams.clear(); + } +} diff --git a/apps/syndie/java/src/net/i2p/syndie/web/RemoteArchiveBean.java b/apps/syndie/java/src/net/i2p/syndie/web/RemoteArchiveBean.java new file mode 100644 index 0000000000000000000000000000000000000000..d5959e6867a1f4c5221a65eeae84c9329c4ccdb5 --- /dev/null +++ b/apps/syndie/java/src/net/i2p/syndie/web/RemoteArchiveBean.java @@ -0,0 +1,379 @@ +package net.i2p.syndie.web; + +import java.io.*; +import java.text.*; +import java.util.*; +import net.i2p.I2PAppContext; +import net.i2p.data.*; +import net.i2p.util.EepGet; +import net.i2p.util.EepGetScheduler; +import net.i2p.syndie.data.*; +import net.i2p.syndie.sml.*; +import net.i2p.syndie.*; + +/** + * + */ +public class RemoteArchiveBean { + private String _remoteSchema; + private String _remoteLocation; + private String _proxyHost; + private int _proxyPort; + private ArchiveIndex _remoteIndex; + private List _statusMessages; + private boolean _fetchIndexInProgress; + + public RemoteArchiveBean() { + reinitialize(); + } + public void reinitialize() { + _remoteSchema = null; + _remoteLocation = null; + _remoteIndex = null; + _fetchIndexInProgress = false; + _proxyHost = null; + _proxyPort = -1; + _statusMessages = new ArrayList(); + } + + public String getRemoteSchema() { return _remoteSchema; } + public String getRemoteLocation() { return _remoteLocation; } + public ArchiveIndex getRemoteIndex() { return _remoteIndex; } + public boolean getFetchIndexInProgress() { return _fetchIndexInProgress; } + public String getStatus() { + StringBuffer buf = new StringBuffer(); + while (_statusMessages.size() > 0) + buf.append(_statusMessages.remove(0)).append("\n"); + return buf.toString(); + } + + public void fetchMetadata(User user, Map parameters) { + String meta = ArchiveViewerBean.getString(parameters, "blog"); + if (meta == null) return; + Set blogs = new HashSet(); + if ("ALL".equals(meta)) { + Set localBlogs = BlogManager.instance().getArchive().getIndex().getUniqueBlogs(); + Set remoteBlogs = _remoteIndex.getUniqueBlogs(); + for (Iterator iter = remoteBlogs.iterator(); iter.hasNext(); ) { + Hash blog = (Hash)iter.next(); + if (!localBlogs.contains(blog)) { + blogs.add(blog); + } + } + } else { + blogs.add(new Hash(Base64.decode(meta.trim()))); + } + List urls = new ArrayList(blogs.size()); + List tmpFiles = new ArrayList(blogs.size()); + for (Iterator iter = blogs.iterator(); iter.hasNext(); ) { + Hash blog = (Hash)iter.next(); + urls.add(buildMetaURL(blog)); + try { + tmpFiles.add(File.createTempFile("fetchMeta", ".txt", BlogManager.instance().getTempDir())); + } catch (IOException ioe) { + _statusMessages.add("Internal error creating temporary file to fetch " + blog.toBase64() + ": " + ioe.getMessage()); + } + } + + for (int i = 0; i < urls.size(); i++) + _statusMessages.add("Scheduling up metadata fetches for " + HTMLRenderer.sanitizeString((String)urls.get(i))); + fetch(urls, tmpFiles, user, new MetadataStatusListener()); + } + + private String buildMetaURL(Hash blog) { + String loc = _remoteLocation.trim(); + int root = loc.lastIndexOf('/'); + return loc.substring(0, root + 1) + blog.toBase64() + "/" + Archive.METADATA_FILE; + } + + public void fetchSelectedEntries(User user, Map parameters) { + String entries[] = ArchiveViewerBean.getStrings(parameters, "entry"); + if ( (entries == null) || (entries.length <= 0) ) return; + List urls = new ArrayList(entries.length); + List tmpFiles = new ArrayList(entries.length); + for (int i = 0; i < entries.length; i++) { + urls.add(buildEntryURL(new BlogURI(entries[i]))); + try { + tmpFiles.add(File.createTempFile("fetchBlog", ".txt", BlogManager.instance().getTempDir())); + } catch (IOException ioe) { + _statusMessages.add("Internal error creating temporary file to fetch " + HTMLRenderer.sanitizeString(entries[i]) + ": " + ioe.getMessage()); + } + } + + for (int i = 0; i < urls.size(); i++) + _statusMessages.add("Scheduling blog post fetching for " + HTMLRenderer.sanitizeString(entries[i])); + fetch(urls, tmpFiles, user, new BlogStatusListener()); + } + + private String buildEntryURL(BlogURI uri) { + String loc = _remoteLocation.trim(); + int root = loc.lastIndexOf('/'); + return loc.substring(0, root + 1) + uri.getKeyHash().toBase64() + "/" + uri.getEntryId() + ".snd"; + } + + public void fetchAllEntries(User user, Map parameters) { + ArchiveIndex localIndex = BlogManager.instance().getArchive().getIndex(); + List uris = new ArrayList(); + List entries = new ArrayList(); + for (Iterator iter = _remoteIndex.getUniqueBlogs().iterator(); iter.hasNext(); ) { + Hash blog = (Hash)iter.next(); + _remoteIndex.selectMatchesOrderByEntryId(entries, blog, null); + for (int i = 0; i < entries.size(); i++) { + BlogURI uri = (BlogURI)entries.get(i); + if (!localIndex.getEntryIsKnown(uri)) + uris.add(uri); + } + entries.clear(); + } + List urls = new ArrayList(uris.size()); + List tmpFiles = new ArrayList(uris.size()); + for (int i = 0; i < uris.size(); i++) { + urls.add(buildEntryURL((BlogURI)uris.get(i))); + try { + tmpFiles.add(File.createTempFile("fetchBlog", ".txt", BlogManager.instance().getTempDir())); + } catch (IOException ioe) { + _statusMessages.add("Internal error creating temporary file to fetch " + HTMLRenderer.sanitizeString(uris.get(i).toString()) + ": " + ioe.getMessage()); + } + } + + for (int i = 0; i < urls.size(); i++) + _statusMessages.add("Fetch all entries: " + HTMLRenderer.sanitizeString((String)urls.get(i))); + fetch(urls, tmpFiles, user, new BlogStatusListener()); + } + + private void fetch(List urls, List tmpFiles, User user, EepGet.StatusListener lsnr) { + EepGetScheduler scheduler = new EepGetScheduler(I2PAppContext.getGlobalContext(), urls, tmpFiles, _proxyHost, _proxyPort, lsnr); + scheduler.fetch(); + } + + public void fetchIndex(User user, String schema, String location) { + _fetchIndexInProgress = true; + _remoteIndex = null; + _remoteLocation = location; + _remoteSchema = schema; + _proxyHost = null; + _proxyPort = -1; + if ("eep".equals(_remoteSchema)) { + _proxyHost = user.getEepProxyHost(); + _proxyPort = user.getEepProxyPort(); + } else if ("web".equals(_remoteSchema)) { + _proxyHost = user.getWebProxyHost(); + _proxyPort = user.getWebProxyPort(); + } else if ("tor".equals(_remoteSchema)) { + _proxyHost = user.getTorProxyHost(); + _proxyPort = user.getTorProxyPort(); + } else { + _statusMessages.add(new String("Remote schema " + HTMLRenderer.sanitizeString(schema) + " currently not supported")); + _fetchIndexInProgress = false; + return; + } + + _statusMessages.add("Fetching index from " + HTMLRenderer.sanitizeString(_remoteLocation)); + File archiveFile = new File(BlogManager.instance().getTempDir(), user.getBlog().toBase64() + "_remoteArchive.txt"); + archiveFile.delete(); + EepGet eep = new EepGet(I2PAppContext.getGlobalContext(), ((_proxyHost != null) && (_proxyPort > 0)), + _proxyHost, _proxyPort, 0, archiveFile.getAbsolutePath(), location); + eep.addStatusListener(new IndexFetcherStatusListener(archiveFile)); + eep.fetch(); + } + + private class IndexFetcherStatusListener implements EepGet.StatusListener { + private File _archiveFile; + public IndexFetcherStatusListener(File file) { + _archiveFile = file; + } + public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) { + _statusMessages.add("Attempt " + currentAttempt + " failed after " + bytesTransferred + (cause != null ? cause.getMessage() : "")); + } + + public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {} + public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) { + _statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " successful"); + _fetchIndexInProgress = false; + ArchiveIndex i = new ArchiveIndex(false); + try { + i.load(_archiveFile); + _statusMessages.add("Archive fetched and loaded"); + _remoteIndex = i; + } catch (IOException ioe) { + _statusMessages.add("Archive is corrupt: " + ioe.getMessage()); + } + } + public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) { + _statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " failed after " + bytesTransferred); + _fetchIndexInProgress = false; + } + } + + private class MetadataStatusListener implements EepGet.StatusListener { + public MetadataStatusListener() {} + public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) { + _statusMessages.add("Attempt " + currentAttempt + " failed after " + bytesTransferred + (cause != null ? cause.getMessage() : "")); + } + + public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {} + public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) { + _statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " successful"); + File info = new File(outputFile); + FileInputStream in = null; + try { + BlogInfo i = new BlogInfo(); + in = new FileInputStream(info); + i.load(in); + boolean ok = BlogManager.instance().getArchive().storeBlogInfo(i); + if (ok) { + _statusMessages.add("Blog info for " + HTMLRenderer.sanitizeString(i.getProperty(BlogInfo.NAME)) + " imported"); + BlogManager.instance().getArchive().reloadInfo(); + } else { + _statusMessages.add("Blog info at " + HTMLRenderer.sanitizeString(url) + " was corrupt / invalid / forged"); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + info.delete(); + } + } + public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) { + _statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " failed after " + bytesTransferred);; + } + } + + private class BlogStatusListener implements EepGet.StatusListener { + public BlogStatusListener() {} + public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) { + _statusMessages.add("Attempt " + currentAttempt + " failed after " + bytesTransferred + (cause != null ? cause.getMessage() : "")); + } + + public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) {} + public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) { + _statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " successful"); + File file = new File(outputFile); + FileInputStream in = null; + try { + EntryContainer c = new EntryContainer(); + in = new FileInputStream(file); + c.load(in); + BlogURI uri = c.getURI(); + if ( (uri == null) || (uri.getKeyHash() == null) ) { + _statusMessages.add("Blog post at " + HTMLRenderer.sanitizeString(url) + " was corrupt - no URI"); + return; + } + Archive a = BlogManager.instance().getArchive(); + BlogInfo info = a.getBlogInfo(uri); + if (info == null) { + _statusMessages.add("Blog post " + uri.toString() + " cannot be imported, as we don't have their blog metadata"); + return; + } + boolean ok = a.storeEntry(c); + if (!ok) { + _statusMessages.add("Blog post at " + url + ": " + uri.toString() + " has an invalid signature"); + return; + } else { + _statusMessages.add("Blog post " + uri.toString() + " imported"); + BlogManager.instance().getArchive().regenerateIndex(); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + file.delete(); + } + } + public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) { + _statusMessages.add("Fetch of " + HTMLRenderer.sanitizeString(url) + " failed after " + bytesTransferred); + } + } + + public void renderDeltaForm(User user, ArchiveIndex localIndex, Writer out) throws IOException { + Archive archive = BlogManager.instance().getArchive(); + StringBuffer buf = new StringBuffer(512); + buf.append("<b>New blogs:</b> <select name=\"blog\"><option value=\"ALL\">All</option>\n"); + Set localBlogs = archive.getIndex().getUniqueBlogs(); + Set remoteBlogs = _remoteIndex.getUniqueBlogs(); + int newBlogs = 0; + for (Iterator iter = remoteBlogs.iterator(); iter.hasNext(); ) { + Hash blog = (Hash)iter.next(); + if (!localBlogs.contains(blog)) { + buf.append("<option value=\"" + blog.toBase64() + "\">" + blog.toBase64() + "</option>\n"); + newBlogs++; + } + } + if (newBlogs > 0) { + out.write(buf.toString()); + out.write("</select> <input type=\"submit\" name=\"action\" value=\"Fetch metadata\" /><br />\n"); + } + + int newEntries = 0; + out.write("<table border=\"1\" width=\"100%\">\n"); + for (Iterator iter = remoteBlogs.iterator(); iter.hasNext(); ) { + Hash blog = (Hash)iter.next(); + buf = new StringBuffer(1024); + int shownEntries = 0; + buf.append("<tr><td colspan=\"5\" align=\"left\" valign=\"top\">\n"); + BlogInfo info = archive.getBlogInfo(blog); + if (info != null) { + buf.append("<a href=\"" + HTMLRenderer.getPageURL(blog, null, -1, -1, -1, user.getShowExpanded(), user.getShowImages()) + "\"><b>" + HTMLRenderer.sanitizeString(info.getProperty(BlogInfo.NAME)) + "</b></a>: " + + HTMLRenderer.sanitizeString(info.getProperty(BlogInfo.DESCRIPTION)) + "\n"); + } else { + buf.append("<b>" + blog.toBase64() + "</b>\n"); + } + buf.append("</td></tr>\n"); + buf.append("<tr><td> </td><td nowrap=\"true\"><b>Posted on</b></td><td nowrap=\"true\"><b>#</b></td><td nowrap=\"true\"><b>Size</b></td><td width=\"90%\" nowrap=\"true\"><b>Tags</b></td></tr>\n"); + List entries = new ArrayList(); + _remoteIndex.selectMatchesOrderByEntryId(entries, blog, null); + for (int i = 0; i < entries.size(); i++) { + BlogURI uri = (BlogURI)entries.get(i); + buf.append("<tr>\n"); + if (!archive.getIndex().getEntryIsKnown(uri)) { + buf.append("<td><input type=\"checkbox\" name=\"entry\" value=\"" + uri.toString() + "\" /></td>\n"); + newEntries++; + shownEntries++; + } else { + String page = HTMLRenderer.getPageURL(blog, null, uri.getEntryId(), -1, -1, + user.getShowExpanded(), user.getShowImages()); + buf.append("<td><a href=\"" + page + "\">(local)</a></td>\n"); + } + buf.append("<td>" + getDate(uri.getEntryId()) + "</td>\n"); + buf.append("<td>" + getId(uri.getEntryId()) + "</td>\n"); + buf.append("<td>" + _remoteIndex.getBlogEntrySizeKB(uri) + "KB</td>\n"); + buf.append("<td>"); + for (Iterator titer = new TreeSet(_remoteIndex.getBlogEntryTags(uri)).iterator(); titer.hasNext(); ) { + String tag = (String)titer.next(); + buf.append("<a href=\"" + HTMLRenderer.getPageURL(blog, tag, -1, -1, -1, user.getShowExpanded(), user.getShowImages()) + "\">" + tag + "</a> \n"); + } + buf.append("</td>\n"); + buf.append("</tr>\n"); + } + if (shownEntries > 0) // skip blogs we have already syndicated + out.write(buf.toString()); + } + out.write("</table>\n"); + if (newEntries > 0) { + out.write("<input type=\"submit\" name=\"action\" value=\"Fetch selected entries\" /> \n"); + out.write("<input type=\"submit\" name=\"action\" value=\"Fetch all new entries\" /> \n"); + } else { + out.write(HTMLRenderer.sanitizeString(_remoteLocation) + " has no new posts to offer us\n"); + } + } + private final SimpleDateFormat _dateFormat = new SimpleDateFormat("yyyy/MM/dd"); + private String getDate(long when) { + synchronized (_dateFormat) { + return _dateFormat.format(new Date(when)); + } + } + + private long getId(long id) { + synchronized (_dateFormat) { + try { + String str = _dateFormat.format(new Date(id)); + long dayBegin = _dateFormat.parse(str).getTime(); + return (id - dayBegin); + } catch (ParseException pe) { + pe.printStackTrace(); + // wtf + return id; + } + } + } +} diff --git a/apps/syndie/jsp/_bodyindex.jsp b/apps/syndie/jsp/_bodyindex.jsp index d06e4cbc0e40889f84a0b085e0e1864b2f4e0dd4..97fe883c84b9008782a48e648f4fcc953959fa3e 100644 --- a/apps/syndie/jsp/_bodyindex.jsp +++ b/apps/syndie/jsp/_bodyindex.jsp @@ -2,7 +2,8 @@ <jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" /> <form action="index.jsp"> <b>Blogs:</b> <%ArchiveViewerBean.renderBlogSelector(user, request.getParameterMap(), out);%> -<input type="submit" value="Refresh" /></form> +<input type="submit" value="Refresh" /> +<input type="submit" name="action" value="<%=ArchiveViewerBean.SEL_ACTION_SET_AS_DEFAULT%>" /></form> <hr /> <%ArchiveViewerBean.renderBlogs(user, request.getParameterMap(), out); out.flush(); %> \ No newline at end of file diff --git a/apps/syndie/jsp/_topnav.jsp b/apps/syndie/jsp/_topnav.jsp index 0f403ebe504a05040bcdf162c5d38d44819e955f..151bd30baf351bd81ab99f5182c3a337a712bf3f 100644 --- a/apps/syndie/jsp/_topnav.jsp +++ b/apps/syndie/jsp/_topnav.jsp @@ -1,3 +1,3 @@ -<td valign="top" align="left" bgcolor="#cccc88" height="10"><a href="index.jsp">Blogs</a></td> -<td valign="top" align="left" bgcolor="#cccc88" height="10">Remote archives</td> -<td valign="top" align="left" bgcolor="#cccc88" height="10">Manage</td> \ No newline at end of file +<td valign="top" align="left" class="syndieTopNavBlogsCell" height="10"><a href="index.jsp">Blogs</a></td> +<td valign="top" align="left" class="syndieTopNavRemoteCell" height="10"><a href="remote.jsp">Remote archives</a></td> +<td valign="top" align="left" class="syndieTopNavManageCell" height="10">Manage</td> \ No newline at end of file diff --git a/apps/syndie/jsp/addaddress.jsp b/apps/syndie/jsp/addaddress.jsp index a252103af3dbac26b39669e487ee1c941b38cc76..6acf3293320172f60914f713d4d38526efe61054 100644 --- a/apps/syndie/jsp/addaddress.jsp +++ b/apps/syndie/jsp/addaddress.jsp @@ -3,6 +3,7 @@ <html> <head> <title>SyndieMedia</title> +<link href="style.jsp" rel="stylesheet" type="text/css" /> </head> <body> <table border="1" cellpadding="0" cellspacing="0" width="100%"> diff --git a/apps/syndie/jsp/externallink.jsp b/apps/syndie/jsp/externallink.jsp index ed05f3c42dfa33a1fa012377f778b55fc56eaf3a..a3e390ac76df3bd225544d1af267b5a46a2e0b58 100644 --- a/apps/syndie/jsp/externallink.jsp +++ b/apps/syndie/jsp/externallink.jsp @@ -2,6 +2,7 @@ <html> <head> <title>SyndieMedia</title> +<link href="style.jsp" rel="stylesheet" type="text/css" /> </head> <body> <table border="1" cellpadding="0" cellspacing="0" width="100%"> diff --git a/apps/syndie/jsp/import.jsp b/apps/syndie/jsp/import.jsp new file mode 100644 index 0000000000000000000000000000000000000000..4fce96933d27d0fc65596f09443d153ef01d264f --- /dev/null +++ b/apps/syndie/jsp/import.jsp @@ -0,0 +1,66 @@ +<%@page import="net.i2p.data.Base64, net.i2p.syndie.web.*, net.i2p.syndie.sml.*, net.i2p.syndie.data.*, net.i2p.syndie.*, org.mortbay.servlet.MultiPartRequest, java.util.*, java.io.*" %> +<jsp:useBean scope="session" class="net.i2p.syndie.data.ArchiveIndex" id="archive" /> +<html> +<head> +<title>SyndieMedia import</title> +<link href="style.jsp" rel="stylesheet" type="text/css" /> +</head> +<body> +<table border="1" cellpadding="0" cellspacing="0" width="100%"> +<tr><td colspan="5" valign="top" align="left"><jsp:include page="_toplogo.jsp" /></td></tr> +<tr><td valign="top" align="left" rowspan="2"><jsp:include page="_leftnav.jsp" /></td> + <jsp:include page="_topnav.jsp" /> + <td valign="top" align="left" rowspan="2"><jsp:include page="_rightnav.jsp" /></td></tr> +<tr><td valign="top" align="left" colspan="3"><% + +String contentType = request.getContentType(); +if ((contentType != null) && (contentType.indexOf("boundary=") != -1) ) { + MultiPartRequest req = new MultiPartRequest(request); + int metaId = 0; + while (true) { + InputStream meta = req.getInputStream("blogmeta" + metaId); + if (meta == null) + break; + if (!BlogManager.instance().importBlogMetadata(meta)) { + System.err.println("blog meta " + metaId + " failed to be imported"); + break; + } + metaId++; + } + int entryId = 0; + while (true) { + InputStream entry = req.getInputStream("blogpost" + entryId); + if (entry == null) + break; + if (!BlogManager.instance().importBlogEntry(entry)) { + System.err.println("blog entry " + entryId + " failed to be imported"); + break; + } + entryId++; + } + + if ( (entryId > 0) || (metaId > 0) ) { + BlogManager.instance().getArchive().regenerateIndex(); + session.setAttribute("index", BlogManager.instance().getArchive().getIndex()); + } +%>Imported <%=entryId%> posts and <%=metaId%> blog metadata files. +<% +} else { %><form action="import.jsp" method="POST" enctype="multipart/form-data"> +Blog metadata 0: <input type="file" name="blogmeta0" /><br /> +Blog metadata 1: <input type="file" name="blogmeta1" /><br /> +Post 0: <input type="file" name="blogpost0" /><br /> +Post 1: <input type="file" name="blogpost1" /><br /> +Post 2: <input type="file" name="blogpost2" /><br /> +Post 3: <input type="file" name="blogpost3" /><br /> +Post 4: <input type="file" name="blogpost4" /><br /> +Post 5: <input type="file" name="blogpost5" /><br /> +Post 6: <input type="file" name="blogpost6" /><br /> +Post 7: <input type="file" name="blogpost7" /><br /> +Post 8: <input type="file" name="blogpost8" /><br /> +Post 9: <input type="file" name="blogpost9" /><br /> +<hr /> +<input type="submit" name="Post" value="Post entry" /> <input type="reset" value="Cancel" /> +<% } %> +</td></tr> +</table> +</body> diff --git a/apps/syndie/jsp/index.jsp b/apps/syndie/jsp/index.jsp index a2ec73d2f205c5f9634d9d5126e8af67dc68f9cf..54985c819a562feaaf5470f9e0124260daf5aa69 100644 --- a/apps/syndie/jsp/index.jsp +++ b/apps/syndie/jsp/index.jsp @@ -2,6 +2,7 @@ <html> <head> <title>SyndieMedia</title> +<link href="style.jsp" rel="stylesheet" type="text/css" /> </head> <body> <table border="1" cellpadding="0" cellspacing="0" width="100%"> diff --git a/apps/syndie/jsp/post.jsp b/apps/syndie/jsp/post.jsp index 5ab5c8b795e5a987c475a4ce648f26a32a1cd8a2..1015d25310c6baee323c3de12515560b2b453e8d 100644 --- a/apps/syndie/jsp/post.jsp +++ b/apps/syndie/jsp/post.jsp @@ -1,8 +1,10 @@ <%@page import="net.i2p.data.Base64, net.i2p.syndie.web.*, net.i2p.syndie.sml.*, net.i2p.syndie.data.*, net.i2p.syndie.*, org.mortbay.servlet.MultiPartRequest, java.util.*" %> <jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" /> +<jsp:useBean scope="session" class="net.i2p.syndie.web.PostBean" id="post" /> <html> <head> <title>SyndieMedia</title> +<link href="style.jsp" rel="stylesheet" type="text/css" /> </head> <body> <table border="1" cellpadding="0" cellspacing="0" width="100%"> @@ -12,62 +14,76 @@ <td valign="top" align="left" rowspan="2"><jsp:include page="_rightnav.jsp" /></td></tr> <tr><td valign="top" align="left" colspan="3"><% -String contentType = request.getContentType(); -if ((contentType != null) && (contentType.indexOf("boundary=") != -1) ) { - if (!user.getAuthenticated()) { %>You must be logged in to post<% - } else { - MultiPartRequest req = new MultiPartRequest(request); - String entrySubject = req.getString("entrysubject"); - String entryTags = req.getString("entrytags"); - String entryText = req.getString("entrytext"); - String entryHeaders = req.getString("entryheaders"); - String replyTo = req.getString(ArchiveViewerBean.PARAM_IN_REPLY_TO); - if ( (replyTo != null) && (replyTo.trim().length() > 0) ) { - byte r[] = Base64.decode(replyTo); - if (r != null) { - if (entryHeaders == null) entryHeaders = HTMLRenderer.HEADER_IN_REPLY_TO + ": " + new String(r); - else entryHeaders = entryHeaders + '\n' + HTMLRenderer.HEADER_IN_REPLY_TO + ": " + new String(r); - } else { - replyTo = null; - } +if (!user.getAuthenticated()) { + %>You must be logged in to post<% +} else { + String confirm = request.getParameter("confirm"); + if ( (confirm != null) && (confirm.equalsIgnoreCase("true")) ) { + BlogURI uri = post.postEntry(); + if (uri != null) { + %>Blog entry <a href="<%=HTMLRenderer.getPageURL(user.getBlog(), null, uri.getEntryId(), -1, -1, + user.getShowExpanded(), user.getShowImages())%>">posted</a>!<% + } else { + %>There was an unknown error posting the entry...<% } - - List fileStreams = new ArrayList(); - List fileNames = new ArrayList(); - List fileTypes = new ArrayList(); - for (int i = 0; i < 32; i++) { - String filename = req.getFilename("entryfile" + i); - if ( (filename != null) && (filename.trim().length() > 0) ) { - fileNames.add(filename.trim()); - fileStreams.add(req.getInputStream("entryfile" + i)); - Hashtable params = req.getParams("entryfile" + i); - String type = "application/octet-stream"; - for (Iterator iter = params.keySet().iterator(); iter.hasNext(); ) { - String cur = (String)iter.next(); - if ("content-type".equalsIgnoreCase(cur)) { - type = (String)params.get(cur); - break; + post.reinitialize(); + post.setUser(user); + } else { + // logged in but not confirmed... + String contentType = request.getContentType(); + if ((contentType != null) && (contentType.indexOf("boundary=") != -1) ) { + // not confirmed but they posted stuff... gobble up what they give + // and display it as a preview (then we show the confirm form) + post.reinitialize(); + post.setUser(user); + + MultiPartRequest req = new MultiPartRequest(request); + String entrySubject = req.getString("entrysubject"); + String entryTags = req.getString("entrytags"); + String entryText = req.getString("entrytext"); + String entryHeaders = req.getString("entryheaders"); + String replyTo = req.getString(ArchiveViewerBean.PARAM_IN_REPLY_TO); + if ( (replyTo != null) && (replyTo.trim().length() > 0) ) { + byte r[] = Base64.decode(replyTo); + if (r != null) { + if (entryHeaders == null) entryHeaders = HTMLRenderer.HEADER_IN_REPLY_TO + ": " + new String(r); + else entryHeaders = entryHeaders + '\n' + HTMLRenderer.HEADER_IN_REPLY_TO + ": " + new String(r); + } else { + replyTo = null; } } - fileTypes.add(type); - } - } - - BlogURI entry = BlogManager.instance().createBlogEntry(user, entrySubject, entryTags, entryHeaders, entryText, fileNames, fileStreams, fileTypes); - if (entry != null) { - // it has been rebuilt... - request.setAttribute("index", BlogManager.instance().getArchive().getIndex()); -%> -Blog entry <a href="<%=HTMLRenderer.getPageURL(user.getBlog(), null, entry.getEntryId(), -1, -1, user.getShowExpanded(), user.getShowImages())%>">posted</a>! -<% } else { %> -There was an error posting... dunno what it was... -<% } - } -} else { %><form action="post.jsp" method="POST" enctype="multipart/form-data"> -Post subject: <input type="text" size="80" name="entrysubject" /><br /> -Post tags: <input type="text" size="20" name="entrytags" /><br /> + + post.setSubject(entrySubject); + post.setTags(entryTags); + post.setText(entryText); + post.setHeaders(entryHeaders); + + for (int i = 0; i < 32; i++) { + String filename = req.getFilename("entryfile" + i); + if ( (filename != null) && (filename.trim().length() > 0) ) { + Hashtable params = req.getParams("entryfile" + i); + String type = "application/octet-stream"; + for (Iterator iter = params.keySet().iterator(); iter.hasNext(); ) { + String cur = (String)iter.next(); + if ("content-type".equalsIgnoreCase(cur)) { + type = (String)params.get(cur); + break; + } + } + post.addAttachment(filename.trim(), req.getInputStream("entryfile" + i), type); + } + } + + post.renderPreview(out); + %><hr />Please <a href="post.jsp?confirm=true">confirm</a> that this is ok. Otherwise, just go back and make changes.<% + } else { + // logged in and not confirmed because they didn't send us anything! + // give 'em a new form +%><form action="post.jsp" method="POST" enctype="multipart/form-data"> +Post subject: <input type="text" size="80" name="entrysubject" value="<%=post.getSubject()%>" /><br /> +Post tags: <input type="text" size="20" name="entrytags" value="<%=post.getTags()%>" /><br /> Post content (in raw SML, no headers):<br /> -<textarea rows="6" cols="80" name="entrytext"></textarea><br /> +<textarea rows="6" cols="80" name="entrytext"><%=post.getText()%></textarea><br /> <b>SML cheatsheet:</b><br /><textarea rows="6" cols="80" readonly="true"> * newlines are newlines are newlines. * all < and > are replaced with their &symbol; @@ -88,7 +104,7 @@ SML headers are newline delimited key=value pairs. Example keys are: * textfont = font to put most text into </textarea><br /> SML post headers:<br /> -<textarea rows="3" cols="80" name="entryheaders"></textarea><br /><% +<textarea rows="3" cols="80" name="entryheaders"><%=post.getHeaders()%></textarea><br /><% String s = request.getParameter(ArchiveViewerBean.PARAM_IN_REPLY_TO); if ( (s != null) && (s.trim().length() > 0) ) {%> <input type="hidden" name="<%=ArchiveViewerBean.PARAM_IN_REPLY_TO%>" value="<%=request.getParameter(ArchiveViewerBean.PARAM_IN_REPLY_TO)%>" /> @@ -105,8 +121,11 @@ Attachment 7: <input type="file" name="entryfile7" /><br /> Attachment 8: <input type="file" name="entryfile8" /><br /> Attachment 9: <input type="file" name="entryfile9" /><br /> <hr /> -<input type="submit" name="Post" value="Post entry" /> <input type="reset" value="Cancel" /> -<% } %> -</td></tr> +<input type="submit" name="Post" value="Preview..." /> <input type="reset" value="Cancel" /> +<% + } // end of the 'logged in, not confirmed, nothing posted' section + } // end of the 'logged in, not confirmed' section +} // end of the 'logged in' section +%></td></tr> </table> </body> diff --git a/apps/syndie/jsp/register.jsp b/apps/syndie/jsp/register.jsp index 4b514a167579f8b1bd9dd1bd315769b676601b97..9c4310984079b06bba55a5cf5f68d43403b246ee 100644 --- a/apps/syndie/jsp/register.jsp +++ b/apps/syndie/jsp/register.jsp @@ -3,6 +3,7 @@ <html> <head> <title>SyndieMedia</title> +<link href="style.jsp" rel="stylesheet" type="text/css" /> </head> <body> <table border="1" cellpadding="0" cellspacing="0" width="100%"> diff --git a/apps/syndie/jsp/remote.jsp b/apps/syndie/jsp/remote.jsp new file mode 100644 index 0000000000000000000000000000000000000000..8270c6418f3f942683e4614cde66593c65de7c36 --- /dev/null +++ b/apps/syndie/jsp/remote.jsp @@ -0,0 +1,58 @@ +<%@page contentType="text/html" import="net.i2p.syndie.web.*" %> +<jsp:useBean scope="session" class="net.i2p.syndie.web.RemoteArchiveBean" id="remote" /> +<jsp:useBean scope="session" class="net.i2p.syndie.User" id="user" /> +<jsp:useBean scope="session" class="net.i2p.syndie.data.ArchiveIndex" id="archive" /> +<html> +<head> +<title>SyndieMedia</title> +<link href="style.jsp" rel="stylesheet" type="text/css" /> +</head> +<body> +<table border="1" cellpadding="0" cellspacing="0" width="100%"> +<tr><td colspan="5" valign="top" align="left"><jsp:include page="_toplogo.jsp" /></td></tr> +<tr><td valign="top" align="left" rowspan="2"><jsp:include page="_leftnav.jsp" /></td> + <jsp:include page="_topnav.jsp" /> + <td valign="top" align="left" rowspan="2"><jsp:include page="_rightnav.jsp" /></td></tr> +<tr><form action="remote.jsp" method="POST"><td valign="top" align="left" colspan="3"> +<% +if (!user.getAuthenticated() || !user.getAllowAccessRemote()) { +%>Sorry, you are not allowed to access remote archives from here. Perhaps you should install Syndie yourself?<% +} else { + %>Import from: +<select name="schema"> + <option value="web">Web</option> + <option value="eep">I2P</option> + <option value="tor">TOR</option> + <option value="freenet">Freenet</option> + <option value="mnet">MNet</option> + <option value="feedspace">Feedspace</option> + <option value="usenet">Usenet</option> +</select> +<input name="location" size="60" /> <input type="submit" name="action" value="Continue..." /> +<% + String action = request.getParameter("action"); + if ("Continue...".equals(action)) { + remote.fetchIndex(user, request.getParameter("schema"), request.getParameter("location")); + } else if ("Fetch metadata".equals(action)) { + remote.fetchMetadata(user, request.getParameterMap()); + } else if ("Fetch selected entries".equals(action)) { + remote.fetchSelectedEntries(user, request.getParameterMap()); + } else if ("Fetch all new entries".equals(action)) { + remote.fetchAllEntries(user, request.getParameterMap()); + } + String msgs = remote.getStatus(); + if ( (msgs != null) && (msgs.length() > 0) ) { %><pre><%=msgs%> +<a href="remote.jsp">Refresh</a></pre><br /><% } + if (remote.getFetchIndexInProgress()) { %><b>Please wait while the index is being fetched +from <%=remote.getRemoteLocation()%></b>. <% + } else if (remote.getRemoteIndex() != null) { + // remote index is NOT null! + %><b><%=remote.getRemoteLocation()%></b>:<br /> +<%remote.renderDeltaForm(user, archive, out);%> +<textarea style="font-size:8pt" rows="5" cols="120"><%=remote.getRemoteIndex()%></textarea><% + } +} +%> +</td></form></tr> +</table> +</body> \ No newline at end of file diff --git a/apps/syndie/jsp/style.jsp b/apps/syndie/jsp/style.jsp new file mode 100644 index 0000000000000000000000000000000000000000..43e5015d53b896d3172e977fbcdfcf342f1645ec --- /dev/null +++ b/apps/syndie/jsp/style.jsp @@ -0,0 +1,2 @@ +<%@page contentType="text/css" %> +<%@include file="syndie.css" %> \ No newline at end of file diff --git a/apps/syndie/jsp/syndie.css b/apps/syndie/jsp/syndie.css new file mode 100644 index 0000000000000000000000000000000000000000..b35a5d4a4cbecf5958a730e1e17f495081e072a3 --- /dev/null +++ b/apps/syndie/jsp/syndie.css @@ -0,0 +1,67 @@ +.syndieEntrySubjectCell { + background-color: #999999; + font-size: 12px; + font-weight: bold; + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + border: 0px; +} +.syndieEntryMetaCell { + background-color: #888888; + font-size: 10px; + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + border: 0px; +} +.syndieEntryAttachmentsCell { + background-color: #aaaaaa; + font-size: 12px; + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + border: 0px; +} +.syndieEntrySummaryCell { + background-color: #eeeeee; + font-size: 12px; + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + border: 0px; +} +.syndieEntryBodyCell { + background-color: #eeeeee; + font-size: 12px; + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + border: 0px; +} +.syndieTopNavBlogsCell { + background-color: #888888; + font-size: 14px; + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + border: 0px; +} +.syndieTopNavRemoteCell { + background-color: #888888; + font-size: 14px; + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + border: 0px; +} +.syndieTopNavManageCell { + background-color: #888888; + font-size: 14px; + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + border: 0px; +} + +body { + margin : 0px; + padding : 0px; + text-align : center; + font-family: Arial, Helvetica, sans-serif; + background-color : #FFFFFF; + color: #000000; + font-size: 12px; +} diff --git a/apps/syndie/jsp/syndie/index.jsp b/apps/syndie/jsp/syndie/index.jsp new file mode 100644 index 0000000000000000000000000000000000000000..5517346b612057755b733ab74eac42310c18dd2c --- /dev/null +++ b/apps/syndie/jsp/syndie/index.jsp @@ -0,0 +1 @@ +<%response.sendRedirect("../index.jsp");%> \ No newline at end of file diff --git a/apps/syndie/jsp/viewmetadata.jsp b/apps/syndie/jsp/viewmetadata.jsp index ede63cf522060cd5dc4722c90c6b23d0e4dbc8fc..7e97f99fe6da027efe4cc14fcf4e41b994ddc9c7 100644 --- a/apps/syndie/jsp/viewmetadata.jsp +++ b/apps/syndie/jsp/viewmetadata.jsp @@ -2,6 +2,7 @@ <html> <head> <title>SyndieMedia</title> +<link href="style.jsp" rel="stylesheet" type="text/css" /> </head> <body> <table border="1" cellpadding="0" cellspacing="0" width="100%"> diff --git a/apps/syndie/jsp/viewtempattachment.jsp b/apps/syndie/jsp/viewtempattachment.jsp new file mode 100644 index 0000000000000000000000000000000000000000..f39b2e3fa9d905113f905c2588532a3f847b7a84 --- /dev/null +++ b/apps/syndie/jsp/viewtempattachment.jsp @@ -0,0 +1,15 @@ +<%@page import="net.i2p.syndie.web.ArchiveViewerBean" %><jsp:useBean +scope="session" class="net.i2p.syndie.web.PostBean" id="post" /><% +String id = request.getParameter(ArchiveViewerBean.PARAM_ATTACHMENT); +if (id != null) { + try { + int attachmentId = Integer.parseInt(id); + if ( (attachmentId < 0) || (attachmentId >= post.getAttachmentCount()) ) { + %>Attachment <%=attachmentId%> does not exist<% + } else { + response.setContentType(post.getContentType(attachmentId)); + post.writeAttachmentData(attachmentId, response.getOutputStream()); + } + } catch (NumberFormatException nfe) {} +} +%> \ No newline at end of file diff --git a/build.xml b/build.xml index 24429ec04d85f64a84db0176c981b01b69d491c9..a21a23aa53f39206ff1abe5648593462c4156742 100644 --- a/build.xml +++ b/build.xml @@ -59,6 +59,7 @@ <copy file="installer/lib/jbigi/jbigi.jar" todir="build" /> <copy file="apps/addressbook/dist/addressbook.war" todir="build/" /> <copy file="apps/susimail/susimail.war" todir="build/" /> + <copy file="apps/syndie/syndie.war" todir="build/" /> <copy file="apps/syndie/java/build/syndie.jar" todir="build/" /> <copy file="apps/syndie/syndie.war" todir="build/" /> </target> @@ -188,6 +189,7 @@ <copy file="build/routerconsole.war" todir="pkg-temp/webapps/" /> <copy file="build/addressbook.war" todir="pkg-temp/webapps/" /> <copy file="build/susimail.war" todir="pkg-temp/webapps/" /> + <copy file="build/syndie.war" todir="pkg-temp/webapps/" /> <copy file="installer/resources/clients.config" todir="pkg-temp/" /> <copy file="installer/resources/eepget" todir="pkg-temp/" /> <copy file="installer/resources/i2prouter" todir="pkg-temp/" /> @@ -286,6 +288,7 @@ <copy file="build/routerconsole.war" todir="pkg-temp/webapps/" /> <copy file="build/addressbook.war" todir="pkg-temp/webapps/" /> <copy file="build/susimail.war" todir="pkg-temp/webapps/" /> + <copy file="build/syndie.war" todir="pkg-temp/webapps/" /> <copy file="history.txt" todir="pkg-temp/" /> <mkdir dir="pkg-temp/docs/" /> <copy file="news.xml" todir="pkg-temp/docs/" /> diff --git a/core/java/src/net/i2p/data/Base64.java b/core/java/src/net/i2p/data/Base64.java index b7da7ed5185ade417e96a49c1a65c865ff354e07..a74f53a9ed6c62a487cc31faf5b0984f4187e9a6 100644 --- a/core/java/src/net/i2p/data/Base64.java +++ b/core/java/src/net/i2p/data/Base64.java @@ -152,6 +152,10 @@ public class Base64 { private static void runApp(String args[]) { try { + if ("encodestring".equalsIgnoreCase(args[0])) { + System.out.println(encode(args[1].getBytes())); + return; + } InputStream in = System.in; OutputStream out = System.out; if (args.length >= 3) { diff --git a/core/java/src/net/i2p/util/EepGetScheduler.java b/core/java/src/net/i2p/util/EepGetScheduler.java new file mode 100644 index 0000000000000000000000000000000000000000..95a532eb3d29e87fa656ab6d7fc6d0668399f094 --- /dev/null +++ b/core/java/src/net/i2p/util/EepGetScheduler.java @@ -0,0 +1,72 @@ +package net.i2p.util; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import net.i2p.I2PAppContext; + +/** + * + */ +public class EepGetScheduler implements EepGet.StatusListener { + private I2PAppContext _context; + private List _urls; + private List _localFiles; + private String _proxyHost; + private int _proxyPort; + private int _curURL; + private EepGet.StatusListener _listener; + + public EepGetScheduler(I2PAppContext ctx, List urls, List localFiles, String proxyHost, int proxyPort, EepGet.StatusListener lsnr) { + _context = ctx; + _urls = urls; + _localFiles = localFiles; + _proxyHost = proxyHost; + _proxyPort = proxyPort; + _curURL = -1; + _listener = lsnr; + } + + public void fetch() { + I2PThread t = new I2PThread(new Runnable() { public void run() { fetchNext(); } }, "EepGetScheduler"); + t.setDaemon(true); + t.start(); + } + + private void fetchNext() { + _curURL++; + if (_curURL >= _urls.size()) return; + String url = (String)_urls.get(_curURL); + String out = EepGet.suggestName(url); + if ( (_localFiles != null) && (_localFiles.size() > _curURL) ) { + File f = (File)_localFiles.get(_curURL); + out = f.getAbsolutePath(); + } else { + if (_localFiles == null) + _localFiles = new ArrayList(_urls.size()); + _localFiles.add(new File(out)); + } + EepGet get = new EepGet(_context, ((_proxyHost != null) && (_proxyPort > 0)), _proxyHost, _proxyPort, 0, out, url); + get.addStatusListener(this); + get.fetch(); + } + + public void attemptFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt, int numRetries, Exception cause) { + _listener.attemptFailed(url, bytesTransferred, bytesRemaining, currentAttempt, numRetries, cause); + } + + public void bytesTransferred(long alreadyTransferred, int currentWrite, long bytesTransferred, long bytesRemaining, String url) { + _listener.bytesTransferred(alreadyTransferred, currentWrite, bytesTransferred, bytesRemaining, url); + } + + public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile) { + _listener.transferComplete(alreadyTransferred, bytesTransferred, bytesRemaining, url, outputFile); + fetchNext(); + } + + public void transferFailed(String url, long bytesTransferred, long bytesRemaining, int currentAttempt) { + _listener.transferFailed(url, bytesTransferred, bytesRemaining, currentAttempt); + fetchNext(); + } + +} diff --git a/history.txt b/history.txt index 8dc717584be9faf2f0f4b0ccebf4df0a30ae9722..879e8c1bbffc5b776a2144ab362bb3918977adb9 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,14 @@ -$Id: history.txt,v 1.227 2005/08/17 15:05:03 jrandom Exp $ +$Id: history.txt,v 1.228 2005/08/21 13:39:06 jrandom Exp $ + +2005-08-23 jrandom + * Removed the concept of "no bandwidth limit" - if none is specified, its + 16KBps in/out. + * Include ack packets in the per-peer cwin throttle (they were part of the + bandwidth limit though). + * Tweak the SSU cwin operation to get more accurrate estimates under + congestions. + * SSU improvements to resend more efficiently. + * Added a basic scheduler to eepget to fetch multiple files sequentially. * 2005-08-21 0.6.0.3 released diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index babaaa0a23b2c8d0e825c040ce75e6e0ba8107d6..41f84e589dfc7a873d8220eff1a6c7c19e8e2c0c 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -15,9 +15,9 @@ import net.i2p.CoreVersion; * */ public class RouterVersion { - public final static String ID = "$Revision: 1.216 $ $Date: 2005/08/17 15:05:03 $"; + public final static String ID = "$Revision: 1.217 $ $Date: 2005/08/21 13:39:05 $"; public final static String VERSION = "0.6.0.3"; - public final static long BUILD = 0; + public final static long BUILD = 1; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION); System.out.println("Router ID: " + RouterVersion.ID); diff --git a/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java b/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java index f2f86548634439a40e4ce5670ec55fe7c75d957a..c8a52d19520df65a742b957db8fb3843866d4c37 100644 --- a/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java +++ b/router/java/src/net/i2p/router/transport/FIFOBandwidthLimiter.java @@ -119,7 +119,9 @@ public class FIFOBandwidthLimiter { */ final void refillBandwidthQueues(long bytesInbound, long bytesOutbound) { if (_log.shouldLog(Log.DEBUG)) - _log.debug("Refilling the queues with " + bytesInbound + "/" + bytesOutbound); + _log.debug("Refilling the queues with " + bytesInbound + "/" + bytesOutbound + ", available " + + _availableInboundBytes + '/' + _availableOutboundBytes + ", max " + + _maxInboundBytes + '/' + _maxOutboundBytes); _availableInboundBytes += bytesInbound; _availableOutboundBytes += bytesOutbound; if (_availableInboundBytes > _maxInboundBytes) { diff --git a/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java b/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java index 2c648c572fdaecfbd826514b73fa17066aabbff9..6e2476548ac5c735628be50df943eb7be953524c 100644 --- a/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java +++ b/router/java/src/net/i2p/router/transport/FIFOBandwidthRefiller.java @@ -25,15 +25,21 @@ class FIFOBandwidthRefiller implements Runnable { public static final String PROP_INBOUND_BANDWIDTH_PEAK = "i2np.bandwidth.inboundBurstKBytes"; public static final String PROP_OUTBOUND_BANDWIDTH_PEAK = "i2np.bandwidth.outboundBurstKBytes"; //public static final String PROP_REPLENISH_FREQUENCY = "i2np.bandwidth.replenishFrequencyMs"; - + + // no longer allow unlimited bandwidth - the user must specify a value, and if they do not, it is 16KBps + public static final int DEFAULT_INBOUND_BANDWIDTH = 16; + public static final int DEFAULT_OUTBOUND_BANDWIDTH = 16; + + public static final int DEFAULT_BURST_SECONDS = 60; + /** For now, until there is some tuning and safe throttling, we set the floor at 6KBps inbound */ - public static final int MIN_INBOUND_BANDWIDTH = 1; + public static final int MIN_INBOUND_BANDWIDTH = 5; /** For now, until there is some tuning and safe throttling, we set the floor at 6KBps outbound */ - public static final int MIN_OUTBOUND_BANDWIDTH = 1; + public static final int MIN_OUTBOUND_BANDWIDTH = 5; /** For now, until there is some tuning and safe throttling, we set the floor at a 10 second burst */ - public static final int MIN_INBOUND_BANDWIDTH_PEAK = 1; + public static final int MIN_INBOUND_BANDWIDTH_PEAK = 10; /** For now, until there is some tuning and safe throttling, we set the floor at a 10 second burst */ - public static final int MIN_OUTBOUND_BANDWIDTH_PEAK = 1; + public static final int MIN_OUTBOUND_BANDWIDTH_PEAK = 10; /** Updating the bandwidth more than once a second is silly. once every 2 or 5 seconds is less so. */ public static final long MIN_REPLENISH_FREQUENCY = 100; @@ -146,6 +152,8 @@ class FIFOBandwidthRefiller implements Runnable { _inboundKBytesPerSecond = in; else _inboundKBytesPerSecond = MIN_INBOUND_BANDWIDTH; + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Updating inbound rate to " + _inboundKBytesPerSecond); } catch (NumberFormatException nfe) { if (_log.shouldLog(Log.WARN)) _log.warn("Invalid inbound bandwidth limit [" + inBwStr @@ -155,6 +163,9 @@ class FIFOBandwidthRefiller implements Runnable { if ( (inBwStr == null) && (_log.shouldLog(Log.DEBUG)) ) _log.debug("Inbound bandwidth limits not specified in the config via " + PROP_INBOUND_BANDWIDTH); } + + if (_inboundKBytesPerSecond <= 0) + _inboundKBytesPerSecond = DEFAULT_INBOUND_BANDWIDTH; } private void updateOutboundRate() { String outBwStr = _context.getProperty(PROP_OUTBOUND_BANDWIDTH); @@ -169,6 +180,8 @@ class FIFOBandwidthRefiller implements Runnable { _outboundKBytesPerSecond = out; else _outboundKBytesPerSecond = MIN_OUTBOUND_BANDWIDTH; + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Updating outbound rate to " + _outboundKBytesPerSecond); } catch (NumberFormatException nfe) { if (_log.shouldLog(Log.WARN)) _log.warn("Invalid outbound bandwidth limit [" + outBwStr @@ -178,6 +191,9 @@ class FIFOBandwidthRefiller implements Runnable { if ( (outBwStr == null) && (_log.shouldLog(Log.DEBUG)) ) _log.debug("Outbound bandwidth limits not specified in the config via " + PROP_OUTBOUND_BANDWIDTH); } + + if (_outboundKBytesPerSecond <= 0) + _outboundKBytesPerSecond = DEFAULT_OUTBOUND_BANDWIDTH; } private void updateInboundPeak() { @@ -203,11 +219,13 @@ class FIFOBandwidthRefiller implements Runnable { if (_log.shouldLog(Log.WARN)) _log.warn("Invalid inbound bandwidth burst limit [" + inBwStr + "]"); + _limiter.setMaxInboundBytes(DEFAULT_BURST_SECONDS * _inboundKBytesPerSecond * 1024); } } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Inbound bandwidth burst limits not specified in the config via " + PROP_INBOUND_BANDWIDTH_PEAK); + _limiter.setMaxInboundBytes(DEFAULT_BURST_SECONDS * _inboundKBytesPerSecond * 1024); } } private void updateOutboundPeak() { @@ -233,11 +251,13 @@ class FIFOBandwidthRefiller implements Runnable { if (_log.shouldLog(Log.WARN)) _log.warn("Invalid outbound bandwidth burst limit [" + outBwStr + "]"); + _limiter.setMaxOutboundBytes(DEFAULT_BURST_SECONDS * _outboundKBytesPerSecond * 1024); } } else { if (_log.shouldLog(Log.DEBUG)) _log.debug("Outbound bandwidth burst limits not specified in the config via " + PROP_OUTBOUND_BANDWIDTH_PEAK); + _limiter.setMaxOutboundBytes(DEFAULT_BURST_SECONDS * _outboundKBytesPerSecond * 1024); } } diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index 31f43266218822c9e57a6c1353e4efa6d1d7b6fb..1efc91fa6205756ed60ef557b8b323163661b285 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -575,6 +575,8 @@ public class EstablishmentManager { if (outboundState != null) { if (outboundState.getLifetime() > MAX_ESTABLISH_TIME) { if (outboundState.getState() != OutboundEstablishState.STATE_CONFIRMED_COMPLETELY) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Lifetime of expired outbound establish: " + outboundState.getLifetime()); while (true) { OutNetMessage msg = outboundState.getNextQueuedMessage(); if (msg == null) diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java b/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java index 8b3d5bb01a9a730d93631e12bf5fed7264c98605..8c7bc78461c6cd8b3835825968f211a0f3a5f788 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundMessageFragments.java @@ -326,7 +326,7 @@ public class OutboundMessageFragments { state.push(); - int rto = peer.getRTO() * state.getPushCount(); + int rto = peer.getRTO();// * state.getPushCount(); state.setNextSendTime(now + rto); if (peer.getSendWindowBytesRemaining() > 0) @@ -338,7 +338,7 @@ public class OutboundMessageFragments { _log.warn("Allocation of " + size + " rejected w/ wsize=" + peer.getSendWindowBytes() + " available=" + peer.getSendWindowBytesRemaining() + " for message " + state.getMessageId() + ": " + state); - state.setNextSendTime((now + 1024) & ~SECOND_MASK); + state.setNextSendTime(now+(_context.random().nextInt(2*ACKSender.ACK_FREQUENCY))); //(now + 1024) & ~SECOND_MASK); if (_log.shouldLog(Log.WARN)) _log.warn("Retransmit after choke for next send time in " + (state.getNextSendTime()-now) + "ms"); _throttle.choke(peer.getRemotePeer()); @@ -435,7 +435,7 @@ public class OutboundMessageFragments { PeerState peer = state.getPeer(); if (peer != null) { // this adjusts the rtt/rto/window/etc - peer.messageACKed(numFragments*state.getFragmentSize(), state.getLifetime(), state.getMaxSends()); + peer.messageACKed(numFragments*state.getFragmentSize(), state.getLifetime(), numSends); if (peer.getSendWindowBytesRemaining() > 0) _throttle.unchoke(peer.getRemotePeer()); } else { diff --git a/router/java/src/net/i2p/router/transport/udp/PeerState.java b/router/java/src/net/i2p/router/transport/udp/PeerState.java index 7aea8613f03626d596a4ecc468f05bd2fac09e76..67bb89221cc250f12e02f78163c99a07016eb840 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerState.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerState.java @@ -93,6 +93,10 @@ public class PeerState { private int _sendBytes; private int _receiveBps; private int _receiveBytes; + private int _sendACKBps; + private int _sendACKBytes; + private int _receiveACKBps; + private int _receiveACKBytes; private long _receivePeriodBegin; private volatile long _lastCongestionOccurred; /** @@ -141,8 +145,11 @@ public class PeerState { private long _packetsTransmitted; /** how many packets were retransmitted within the last RETRANSMISSION_PERIOD_WIDTH packets */ private long _packetsRetransmitted; + /** how many packets were transmitted within the last RETRANSMISSION_PERIOD_WIDTH packets */ + private long _packetsPeriodTransmitted; + private int _packetsPeriodRetransmitted; private int _packetRetransmissionRate; - /** what was the $packetsTransmitted when the current RETRANSMISSION_PERIOD_WIDTH began */ + /** at what time did we last break off the retransmission counter period */ private long _retransmissionPeriodStart; /** how many dup packets were received within the last RETRANSMISSION_PERIOD_WIDTH packets */ private long _packetsReceivedDuplicate; @@ -163,7 +170,7 @@ public class PeerState { * of 608 */ private static final int DEFAULT_MTU = 608;//600; //1500; - private static final int MIN_RTO = 1000 + ACKSender.ACK_FREQUENCY; + private static final int MIN_RTO = 500 + ACKSender.ACK_FREQUENCY; private static final int MAX_RTO = 3000; // 5000; public PeerState(I2PAppContext ctx) { @@ -373,6 +380,10 @@ public class PeerState { return _consecutiveFailedSends; } + /** how fast we are sending *ack* packets */ + public int getSendACKBps() { return _sendACKBps; } + public int getReceiveACKBps() { return _receiveACKBps; } + /** * have all of the packets received in the current second requested that * the previous second's ACKs be sent? @@ -384,14 +395,20 @@ public class PeerState { * cannot. If it is not decremented, the window size remaining is * not adjusted at all. */ - public boolean allocateSendingBytes(int size) { + public boolean allocateSendingBytes(int size) { return allocateSendingBytes(size, false); } + public boolean allocateSendingBytes(int size, boolean isForACK) { long now = _context.clock().now(); long duration = now - _lastSendRefill; if (duration >= 1000) { _sendWindowBytesRemaining = _sendWindowBytes; _sendBytes += size; _sendBps = (int)(0.9f*(float)_sendBps + 0.1f*((float)_sendBytes * (1000f/(float)duration))); + if (isForACK) { + _sendACKBytes += size; + _sendACKBps = (int)(0.9f*(float)_sendACKBps + 0.1f*((float)_sendACKBytes * (1000f/(float)duration))); + } _sendBytes = 0; + _sendACKBytes = 0; _lastSendRefill = now; } //if (true) return true; @@ -399,6 +416,8 @@ public class PeerState { _sendWindowBytesRemaining -= size; _sendBytes += size; _lastSendTime = now; + if (isForACK) + _sendACKBytes += size; return true; } else { return false; @@ -432,14 +451,17 @@ public class PeerState { public int getSlowStartThreshold() { return _slowStartThreshold; } /** we received the message specified completely */ - public void messageFullyReceived(Long messageId, int bytes) { - if (bytes > 0) + public void messageFullyReceived(Long messageId, int bytes) { messageFullyReceived(messageId, bytes, false); } + public void messageFullyReceived(Long messageId, int bytes, boolean isForACK) { + if (bytes > 0) { _receiveBytes += bytes; - else { - if (_retransmissionPeriodStart + RETRANSMISSION_PERIOD_WIDTH < _packetsReceived) { + if (isForACK) + _receiveACKBytes += bytes; + } else { + if (_retransmissionPeriodStart + 1000 < _context.clock().now()) { _packetsReceivedDuplicate++; } else { - _retransmissionPeriodStart = _packetsReceived; + _retransmissionPeriodStart = _context.clock().now(); _packetsReceivedDuplicate = 1; } } @@ -448,6 +470,9 @@ public class PeerState { long duration = now - _receivePeriodBegin; if (duration >= 1000) { _receiveBps = (int)(0.9f*(float)_receiveBps + 0.1f*((float)_receiveBytes * (1000f/(float)duration))); + if (isForACK) + _receiveACKBps = (int)(0.9f*(float)_receiveACKBps + 0.1f*((float)_receiveACKBytes * (1000f/(float)duration))); + _receiveACKBytes = 0; _receiveBytes = 0; _receivePeriodBegin = now; _context.statManager().addRateData("udp.receiveBps", _receiveBps, 0); @@ -480,20 +505,21 @@ public class PeerState { */ private boolean congestionOccurred() { long now = _context.clock().now(); - if (_lastCongestionOccurred + 10*1000 > now) - return false; // only shrink once every 10 seconds + if (_lastCongestionOccurred + 5*1000 > now) + return false; // only shrink once every 5 seconds _lastCongestionOccurred = now; _context.statManager().addRateData("udp.congestionOccurred", _sendWindowBytes, _sendBps); + int congestionAt = _sendWindowBytes; //if (true) // _sendWindowBytes -= 10000; //else - _sendWindowBytes = (_sendWindowBytes*2) / 3; + _sendWindowBytes = _sendWindowBytes/4; //(_sendWindowBytes*2) / 3; if (_sendWindowBytes < MINIMUM_WINDOW_BYTES) _sendWindowBytes = MINIMUM_WINDOW_BYTES; - if (_sendWindowBytes < _slowStartThreshold) - _slowStartThreshold = _sendWindowBytes; + //if (congestionAt/2 < _slowStartThreshold) + _slowStartThreshold = congestionAt/2; return true; } @@ -595,24 +621,34 @@ public class PeerState { public void messageACKed(int bytesACKed, long lifetime, int numSends) { _consecutiveFailedSends = 0; _lastFailedSendPeriod = -1; - if (_sendWindowBytes <= _slowStartThreshold) { - _sendWindowBytes += bytesACKed; - } else { - double prob = ((double)bytesACKed) / ((double)_sendWindowBytes); - if (_context.random().nextDouble() <= prob) + if (numSends < 2) { + if (_sendWindowBytes <= _slowStartThreshold) { _sendWindowBytes += bytesACKed; + } else { + if (false) { + _sendWindowBytes += 16; // why 16? + } else { + float prob = ((float)bytesACKed) / ((float)_sendWindowBytes); + float v = _context.random().nextFloat(); + if (v < 0) v = 0-v; + if (v <= prob) + _sendWindowBytes += bytesACKed; + } + } } if (_sendWindowBytes > MAX_SEND_WINDOW_BYTES) _sendWindowBytes = MAX_SEND_WINDOW_BYTES; _lastReceiveTime = _context.clock().now(); - if (_sendWindowBytesRemaining + bytesACKed <= _sendWindowBytes) - _sendWindowBytesRemaining += bytesACKed; - else - _sendWindowBytesRemaining = _sendWindowBytes; + if (false) { + if (_sendWindowBytesRemaining + bytesACKed <= _sendWindowBytes) + _sendWindowBytesRemaining += bytesACKed; + else + _sendWindowBytesRemaining = _sendWindowBytes; + } _messagesSent++; - if (numSends <= 2) + if (numSends < 2) recalculateTimeouts(lifetime); else _log.warn("acked after numSends=" + numSends + " w/ lifetime=" + lifetime + " and size=" + bytesACKed); @@ -643,11 +679,14 @@ public class PeerState { /** we are resending a packet, so lets jack up the rto */ public void messageRetransmitted(int packets) { - if (_retransmissionPeriodStart + RETRANSMISSION_PERIOD_WIDTH < _packetsTransmitted) { + long now = _context.clock().now(); + if (_retransmissionPeriodStart + 1000 <= now) { _packetsRetransmitted += packets; } else { _packetRetransmissionRate = (int)((float)(0.9f*_packetRetransmissionRate) + (float)(0.1f*_packetsRetransmitted)); - _retransmissionPeriodStart = _packetsTransmitted; + //_packetsPeriodTransmitted = _packetsTransmitted - _retransmissionPeriodStart; + _packetsPeriodRetransmitted = (int)_packetsRetransmitted; + _retransmissionPeriodStart = now; _packetsRetransmitted = packets; } congestionOccurred(); @@ -655,10 +694,13 @@ public class PeerState { //_rto *= 2; } public void packetsTransmitted(int packets) { + long now = _context.clock().now(); _packetsTransmitted += packets; - if (_retransmissionPeriodStart + RETRANSMISSION_PERIOD_WIDTH > _packetsTransmitted) { + //_packetsPeriodTransmitted += packets; + if (_retransmissionPeriodStart + 1000 <= now) { _packetRetransmissionRate = (int)((float)(0.9f*_packetRetransmissionRate) + (float)(0.1f*_packetsRetransmitted)); - _retransmissionPeriodStart = _packetsTransmitted; + _retransmissionPeriodStart = 0; + _packetsPeriodRetransmitted = (int)_packetsRetransmitted; _packetsRetransmitted = 0; } } @@ -673,6 +715,8 @@ public class PeerState { public long getMessagesReceived() { return _messagesReceived; } public long getPacketsTransmitted() { return _packetsTransmitted; } public long getPacketsRetransmitted() { return _packetsRetransmitted; } + public long getPacketsPeriodTransmitted() { return _packetsPeriodTransmitted; } + public int getPacketsPeriodRetransmitted() { return _packetsPeriodRetransmitted; } /** avg number of packets retransmitted for every 100 packets */ public long getPacketRetransmissionRate() { return _packetRetransmissionRate; } public long getPacketsReceived() { return _packetsReceived; } diff --git a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java index b57c36bc47d98ea3ccd10c874024939b7f067f03..aa73ded7748d55f7cc1ecc85ac59b969c31c8d18 100644 --- a/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java +++ b/router/java/src/net/i2p/router/transport/udp/PeerTestManager.java @@ -132,6 +132,7 @@ class PeerTestManager { */ private void receiveTestReply(RemoteHostId from, UDPPacketReader.PeerTestReader testInfo) { PeerTestState test = _currentTest; + if (test == null) return; if ( (DataHelper.eq(from.getIP(), test.getBobIP().getAddress())) && (from.getPort() == test.getBobPort()) ) { byte ip[] = new byte[testInfo.readIPSize()]; testInfo.readIP(ip, 0); diff --git a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java index b3efba8baa59bb77b44f49f44dab52ef2992a042..c0dd5e4f6820f7b1e4024e3cafbdd8d7d9bc7608 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPReceiver.java @@ -40,9 +40,12 @@ public class UDPReceiver { _runner = new Runner(); _context.statManager().createRateStat("udp.receivePacketSize", "How large packets received are", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); _context.statManager().createRateStat("udp.droppedInbound", "How many packet are queued up but not yet received when we drop", "udp", new long[] { 60*1000, 10*60*1000, 60*60*1000 }); + _context.statManager().createRateStat("udp.droppedInboundProbabalistically", "How many packet we drop probabalistically (to simulate failures)", "udp", new long[] { 60*1000, 5*60*1000, 10*60*1000, 60*60*1000 }); + _context.statManager().createRateStat("udp.acceptedInboundProbabalistically", "How many packet we accept probabalistically (to simulate failures)", "udp", new long[] { 60*1000, 5*60*1000, 10*60*1000, 60*60*1000 }); } public void startup() { + adjustDropProbability(); _keepRunning = true; I2PThread t = new I2PThread(_runner, _name); t.setDaemon(true); @@ -57,6 +60,18 @@ public class UDPReceiver { } } + private void adjustDropProbability() { + String p = _context.getProperty("i2np.udp.dropProbability"); + if (p != null) { + try { + ARTIFICIAL_DROP_PROBABILITY = Float.parseFloat(p); + } catch (NumberFormatException nfe) {} + if (ARTIFICIAL_DROP_PROBABILITY < 0) ARTIFICIAL_DROP_PROBABILITY = 0; + } else { + ARTIFICIAL_DROP_PROBABILITY = 0; + } + } + /** * Replace the old listen port with the new one, returning the old. * NOTE: this closes the old socket so that blocking calls unblock! @@ -69,17 +84,26 @@ public class UDPReceiver { /** if a packet been sitting in the queue for a full second (meaning the handlers are overwhelmed), drop subsequent packets */ private static final long MAX_QUEUE_PERIOD = 1*1000; - private static final float ARTIFICIAL_DROP_PROBABILITY = 0.0f; // 0.02f; // 0.0f; + private static float ARTIFICIAL_DROP_PROBABILITY = 0.0f; // 0.02f; // 0.0f; private static final int ARTIFICIAL_DELAY = 0; // 100; private static final int ARTIFICIAL_DELAY_BASE = 0; //100; private int receive(UDPPacket packet) { + //adjustDropProbability(); + if (ARTIFICIAL_DROP_PROBABILITY > 0) { // the first check is to let the compiler optimize away this // random block on the live system when the probability is == 0 - if (_context.random().nextFloat() <= ARTIFICIAL_DROP_PROBABILITY) + int v = _context.random().nextInt(1000); + if (v < ARTIFICIAL_DROP_PROBABILITY*1000) { + if (_log.shouldLog(Log.ERROR)) + _log.error("Drop with v=" + v + " p=" + ARTIFICIAL_DROP_PROBABILITY + " packet size: " + packet.getPacket().getLength()); + _context.statManager().addRateData("udp.droppedInboundProbabalistically", 1, 0); return -1; + } else { + _context.statManager().addRateData("udp.acceptedInboundProbabalistically", 1, 0); + } } if ( (ARTIFICIAL_DELAY > 0) || (ARTIFICIAL_DELAY_BASE > 0) ) {