diff --git a/apps/syndie/java/src/net/i2p/syndie/data/BlogInfoData.java b/apps/syndie/java/src/net/i2p/syndie/data/BlogInfoData.java index 69e9baee4f577894f3b4eb46ed473bca653d3e5b..673dd08259603cbf8f59f9174149d1a1a6035628 100644 --- a/apps/syndie/java/src/net/i2p/syndie/data/BlogInfoData.java +++ b/apps/syndie/java/src/net/i2p/syndie/data/BlogInfoData.java @@ -25,6 +25,8 @@ public class BlogInfoData { /** identifies a post as being a blog info data, not a content bearing post */ public static final String TAG = "BlogInfoData"; + public static final int MAX_LOGO_SIZE = 128*1024; + public BlogInfoData() {} public BlogURI getEntryId() { return _dataEntryId; } 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 db9ed3b497c46196a07eab4e1ac8942d5684b814..a7235fa13d273b4c7431e126273dc22b38149881 100644 --- a/apps/syndie/java/src/net/i2p/syndie/sml/HTMLRenderer.java +++ b/apps/syndie/java/src/net/i2p/syndie/sml/HTMLRenderer.java @@ -1026,7 +1026,7 @@ public class HTMLRenderer extends EventReceiverImpl { "&" + ArchiveViewerBean.PARAM_ENTRY + "=" + _entry.getURI().getEntryId() + "&" + ArchiveViewerBean.PARAM_ATTACHMENT + "=" + id; } - + public String getMetadataURL() { if (_entry == null) return "unknown"; return getMetadataURL(_entry.getURI().getKeyHash()); diff --git a/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigBean.java b/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigBean.java index 4ad6c7c8f29c64b42716f76d7ed2e5286720396d..a568ab3838c55c503caf49a08362241f88be57f2 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigBean.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigBean.java @@ -140,10 +140,13 @@ public class BlogConfigBean { } public File getLogo() { return _logo; } public void setLogo(File logo) { - if ( (logo != null) && (logo.length() > 128*1024) ) { - _log.error("Refusing a logo more than 128KB"); + if ( (logo != null) && (logo.length() > BlogInfoData.MAX_LOGO_SIZE) ) { + _log.error("Refusing a logo of size " + logo.length()); + logo.delete(); return; } + if (_logo != null) + _logo.delete(); _logo = logo; _updated = true; } @@ -198,8 +201,10 @@ public class BlogConfigBean { public boolean publishChanges() { FileInputStream logo = null; try { - if (_logo != null) + if (_logo != null) { logo = new FileInputStream(_logo); + _log.debug("Logo file is: " + _logo.length() + "bytes @ " + _logo.getAbsolutePath()); + } InputStream styleStream = createStyleStream(); InputStream groupStream = createGroupStream(); @@ -240,6 +245,7 @@ public class BlogConfigBean { // ok great, published locally, though should we push it to others? _log.info("Blog summary updated for " + _user + " in " + uri.toString()); setUser(_user); + _log.debug("Updated? " + _updated); return true; } } else { @@ -255,6 +261,7 @@ public class BlogConfigBean { } finally { if (logo != null) try { logo.close(); } catch (IOException ioe) {} // the other streams are in-memory, drop with the scope + if (_logo != null) _logo.delete(); } return false; } @@ -280,4 +287,8 @@ public class BlogConfigBean { } return new ByteArrayInputStream(DataHelper.getUTF8(buf)); } + + protected void finalize() { + if (_logo != null) _logo.delete(); + } } diff --git a/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigServlet.java index af71fbbc22d9d180ff2a471a28b6c02e28d74427..98b06483e6cd23fce23b5b2b09c4def9d240415f 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigServlet.java @@ -24,21 +24,28 @@ public class BlogConfigServlet extends BaseServlet { public static final String PARAM_CONFIG_SCREEN = "screen"; public static final String SCREEN_REFERENCES = "references"; public static final String SCREEN_IMAGES = "images"; - protected void renderServletDetails(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index, - int threadOffset, BlogURI visibleEntry, Archive archive) throws IOException { - if ( (user == null) || (!user.getAuthenticated() && !BlogManager.instance().isSingleUser())) { - out.write("You must be logged in to edit your profile"); - return; - } + + public static BlogConfigBean getConfigBean(HttpServletRequest req, User user) { BlogConfigBean bean = (BlogConfigBean)req.getSession().getAttribute(ATTR_CONFIG_BEAN); if (bean == null) { bean = new BlogConfigBean(); bean.setUser(user); req.getSession().setAttribute(ATTR_CONFIG_BEAN, bean); } + return bean; + } + public static BlogConfigBean getConfigBean(HttpServletRequest req) { + return (BlogConfigBean)req.getSession().getAttribute(ATTR_CONFIG_BEAN); + } + + protected void renderServletDetails(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index, + int threadOffset, BlogURI visibleEntry, Archive archive) throws IOException { + if ( (user == null) || (!user.getAuthenticated() && !BlogManager.instance().isSingleUser())) { + out.write("You must be logged in to edit your profile"); + return; + } - // handle actions here... - // on done handling + BlogConfigBean bean = getConfigBean(req, user); String screen = req.getParameter(PARAM_CONFIG_SCREEN); if (screen == null) @@ -50,17 +57,70 @@ public class BlogConfigServlet extends BaseServlet { StringBuffer buf = handleOtherAuthedActions(user, req, bean); if (buf != null) out.write(buf.toString()); } else { + String contentType = req.getContentType(); + if (!empty(contentType) && (contentType.indexOf("boundary=") != -1)) { + StringBuffer buf = handlePost(user, req, bean); + if (buf != null) out.write(buf.toString()); + } } if (bean.isUpdated()) showCommitForm(req, out); if (SCREEN_REFERENCES.equals(screen)) { displayReferencesScreen(req, out, user, bean); + } else if (SCREEN_IMAGES.equals(screen)) { + displayImagesScreen(req, out, user, bean); } else { displayUnknownScreen(out, screen); } out.write("</td></tr>\n"); } + private StringBuffer handlePost(User user, HttpServletRequest rawRequest, BlogConfigBean bean) throws IOException { + StringBuffer rv = new StringBuffer(64); + MultiPartRequest req = new MultiPartRequest(rawRequest); + if (authAction(req.getString(PARAM_AUTH_ACTION))) { + // read in the logo if specified + String filename = req.getFilename("newLogo"); + if ( (filename != null) && (filename.trim().length() > 0) ) { + Hashtable params = req.getParams("newLogo"); + String type = "image/png"; + for (Iterator iter = params.keySet().iterator(); iter.hasNext(); ) { + String cur = (String)iter.next(); + if ("content-type".equalsIgnoreCase(cur)) { + type = (String)params.get(cur); + break; + } + } + InputStream logoSrc = req.getInputStream("newLogo"); + + File tmpLogo = File.createTempFile("blogLogo", ".png", BlogManager.instance().getTempDir()); + FileOutputStream out = null; + try { + out = new FileOutputStream(tmpLogo); + byte buf[] = new byte[4096]; + int read = 0; + while ( (read = logoSrc.read(buf)) != -1) + out.write(buf, 0, read); + } finally { + if (out != null) try { out.close(); } catch (IOException ioe) {} + } + + long len = tmpLogo.length(); + if (len > BlogInfoData.MAX_LOGO_SIZE) { + tmpLogo.delete(); + rv.append("Proposed logo is too large (" + len + ", max of " + BlogInfoData.MAX_LOGO_SIZE + ")<br />\n"); + } else { + bean.setLogo(tmpLogo); + rv.append("Logo updated<br />"); + } + } else { + // logo not specified + } + } else { + // noop + } + return rv; + } private void showCommitForm(HttpServletRequest req, PrintWriter out) throws IOException { out.write("<form action=\"" + req.getRequestURI() + "\" method=\"GET\">\n"); @@ -186,6 +246,18 @@ public class BlogConfigServlet extends BaseServlet { } } + private void displayImagesScreen(HttpServletRequest req, PrintWriter out, User user, BlogConfigBean bean) throws IOException { + out.write("<form action=\"" + getScreenURL(req, SCREEN_IMAGES, false) + "\" method=\"POST\" enctype=\"multipart/form-data\">\n"); + writeAuthActionFields(out); + + File logo = bean.getLogo(); + if (logo != null) + out.write("Blog logo: <img src=\"" + ViewBlogServlet.getLogoURL(user.getBlog()) + "\" alt=\"Your blog's logo\" /><br />\n"); + out.write("New logo: <input type=\"file\" name=\"newLogo\" title=\"PNG or JPG format logo\" /><br />\n"); + out.write("<input type=\"submit\" name=\"action\" value=\"Save changes\">\n"); + out.write("</form>\n"); + } + protected StringBuffer handleOtherAuthedActions(User user, HttpServletRequest req, BlogConfigBean bean) { StringBuffer buf = new StringBuffer(); req.setAttribute(getClass().getName() + ".output", buf); diff --git a/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java b/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java index f0d24f43f4c77b90aed04e30e1202936e992809c..6cd475b2d5aec3548147ebf554fd2b3b0aba7be0 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/PostBean.java @@ -5,8 +5,7 @@ import java.util.*; import net.i2p.I2PAppContext; import net.i2p.client.naming.PetName; import net.i2p.syndie.*; -import net.i2p.syndie.data.BlogURI; -import net.i2p.syndie.data.EntryContainer; +import net.i2p.syndie.data.*; import net.i2p.syndie.sml.HTMLPreviewRenderer; import net.i2p.syndie.sml.HTMLRenderer; import net.i2p.util.Log; @@ -112,7 +111,19 @@ public class PostBean { if ( (pn != null) && ("syndiearchive".equals(pn.getProtocol())) ) { RemoteArchiveBean r = new RemoteArchiveBean(); Map params = new HashMap(); - params.put("localentry", new String[] { uri.toString() }); + + String entries[] = null; + BlogInfo info = BlogManager.instance().getArchive().getBlogInfo(uri); + if (info != null) { + String str = info.getProperty(BlogInfo.SUMMARY_ENTRY_ID); + if (str != null) { + entries = new String[] { uri.toString(), str }; + } + } + if (entries == null) + entries = new String[] { uri.toString() }; + + params.put("localentry", entries); String proxyHost = BlogManager.instance().getDefaultProxyHost(); String port = BlogManager.instance().getDefaultProxyPort(); int proxyPort = 4444; diff --git a/apps/syndie/java/src/net/i2p/syndie/web/ProfileServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/ProfileServlet.java index f4ee59228b03e1f5395053775035d6bea9c94ac0..ebebc4914be71ce7300f62fd0f639fa3001673ed 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/ProfileServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/ProfileServlet.java @@ -104,7 +104,6 @@ public class ProfileServlet extends BaseServlet { out.write("<tr><td colspan=\"3\"><input type=\"submit\" name=\"action\" value=\"Update profile\" /></td></tr>\n"); out.write("</form>\n"); - out.write("<tr><td colspan=\"3\"><a href=\"configblog.jsp\">Configure your blog</a></td></tr>\n"); } private void renderProfile(User user, String baseURI, PrintWriter out, Hash author, Archive archive) throws IOException { diff --git a/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogServlet.java index f4c93a39296b325542d9ed639cc3092099c80e94..1e0bc704f42fd321838b4800a05246ccbf723813 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogServlet.java @@ -31,6 +31,8 @@ public class ViewBlogServlet extends BaseServlet { public static final String PARAM_TAG = "tag"; /** $blogHash/$entryId/$attachmentId */ public static final String PARAM_ATTACHMENT = "attachment"; + /** image within the BlogInfoData to load (e.g. logo.png, icon_$tagHash.png, etc) */ + public static final String PARAM_IMAGE = "image"; public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); @@ -40,7 +42,15 @@ public class ViewBlogServlet extends BaseServlet { if (renderAttachment(req, resp, attachment)) return; } - //todo: take care of logo requests, etc + String img = req.getParameter(PARAM_IMAGE); + if (img != null) { + boolean rendered = renderUpdatedImage(img, req, resp); + if (!rendered) + rendered = renderPublishedImage(img, req, resp); + if (!rendered) + rendered = renderDefaultImage(img, req, resp); + if (rendered) return; + } super.service(req, resp); } @@ -179,16 +189,17 @@ public class ViewBlogServlet extends BaseServlet { if (content != null) out.write(content); } + public static String getLogoURL(Hash blog) { + return "blog.jsp?" + PARAM_BLOG + "=" + blog.toBase64() + "&" + + PARAM_IMAGE + "=" + BlogInfoData.ATTACHMENT_LOGO; + } + private void renderHeader(User user, HttpServletRequest req, PrintWriter out, BlogInfo info, BlogInfoData data, String title, String desc) throws IOException { out.write("<body class=\"syndieBlog\">\n<span style=\"display: none\">" + "<a href=\"#content\" title=\"Skip to the blog content\">Content</a></span>\n"); renderNavBar(user, req, out); out.write("<div class=\"syndieBlogHeader\">\n"); - if (data != null) { - if (data.isLogoSpecified()) { - out.write("<img src=\"logo.png\" alt=\"\" />\n"); - } - } + out.write("<img class=\"syndieBlogLogo\" src=\"" + getLogoURL(info.getKey().calculateHash()) + "\" alt=\"\" />\n"); String name = desc; if ( (name == null) || (name.trim().length() <= 0) ) name = title; @@ -454,6 +465,135 @@ public class ViewBlogServlet extends BaseServlet { } catch (NumberFormatException nfe) {} return false; } + + + private boolean renderUpdatedImage(String requestedImage, HttpServletRequest req, HttpServletResponse resp) throws IOException { + BlogConfigBean bean = BlogConfigServlet.getConfigBean(req); + if ( (bean != null) && (bean.isUpdated()) && (bean.getLogo() != null) ) { + // the updated image only affects *our* blog... + User u = bean.getUser(); + if (u != null) { + String reqBlog = req.getParameter(PARAM_BLOG); + if ( (reqBlog == null) || (u.getBlog().toBase64().equals(reqBlog)) ) { + if (BlogInfoData.ATTACHMENT_LOGO.equals(requestedImage)) { + File logo = bean.getLogo(); + if (logo != null) { + byte buf[] = new byte[4096]; + resp.setContentType("image/png"); + resp.setContentLength((int)logo.length()); + OutputStream out = resp.getOutputStream(); + FileInputStream in = null; + try { + in = new FileInputStream(logo); + int read = 0; + while ( (read = in.read(buf)) != -1) + out.write(buf, 0, read); + _log.debug("Done writing the updated full length logo"); + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + if (out != null) try { out.close(); } catch (IOException ioe) {} + } + _log.debug("Returning from writing the updated full length logo"); + return true; + } + } else { + // ok, the blogConfigBean doesn't let people configure other things yet... fall through + } + } + } + } + return false; + } + + private boolean renderPublishedImage(String requestedImage, HttpServletRequest req, HttpServletResponse resp) throws IOException { + // nothing matched in the updated config, lets look at the current published info + String blog = req.getParameter(PARAM_BLOG); + if (blog != null) { + Archive archive = BlogManager.instance().getArchive(); + byte h[] = Base64.decode(blog); + if ( (h != null) && (h.length == Hash.HASH_LENGTH) ) { + Hash blogHash = new Hash(h); + BlogInfo info = archive.getBlogInfo(blogHash); + String entryId = info.getProperty(BlogInfo.SUMMARY_ENTRY_ID); + _log.debug("Author's entryId: " + entryId); + if (entryId != null) { + BlogURI dataURI = new BlogURI(entryId); + EntryContainer entry = archive.getEntry(dataURI); + if (entry != null) { + BlogInfoData data = new BlogInfoData(); + try { + data.load(entry); + + _log.debug("Blog info data loaded from: " + entryId); + Attachment toWrite = null; + if (BlogInfoData.ATTACHMENT_LOGO.equals(requestedImage)) { + toWrite = data.getLogo(); + } else { + toWrite = data.getOtherAttachment(requestedImage); + } + if (toWrite != null) { + resp.setContentType("image/png"); + resp.setContentLength(toWrite.getDataLength()); + InputStream in = null; + OutputStream out = null; + try { + in = toWrite.getDataStream(); + out = resp.getOutputStream(); + byte buf[] = new byte[4096]; + int read = -1; + while ( (read = in.read(buf)) != -1) + out.write(buf, 0, read); + + _log.debug("Write image from: " + entryId); + return true; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + if (out != null) try { out.close(); } catch (IOException ioe) {} + } + } + } catch (IOException ioe) { + _log.debug("Error reading/writing: " + entryId, ioe); + data = null; + } + } + } + } + } + return false; + } + + /** 1px png, base64 encoded, used if they asked for an image that we dont know of */ + private static final byte BLANK_IMAGE[] = Base64.decode("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQI12NgYAAAAAMAASDVlMcAAAAASUVORK5CYII="); + private boolean renderDefaultImage(String requestedImage, HttpServletRequest req, HttpServletResponse resp) throws IOException { + if (requestedImage.equals("logo.png")) { + InputStream in = req.getSession().getServletContext().getResourceAsStream("/images/default_blog_logo.png"); + if (in != null) { + resp.setContentType("image/png"); + OutputStream out = resp.getOutputStream(); + try { + byte buf[] = new byte[4096]; + int read = -1; + while ( (read = in.read(buf)) != -1) + out.write(buf, 0, read); + _log.debug("Done writing default logo"); + } finally { + try { in.close(); } catch (IOException ioe) {} + try { out.close(); } catch (IOException ioe) {} + return true; + } + } + } + resp.setContentType("img.png"); + resp.setContentLength(BLANK_IMAGE.length); + OutputStream out = resp.getOutputStream(); + try { + out.write(BLANK_IMAGE); + } finally { + try { out.close(); } catch (IOException ioe) {} + } + _log.debug("Done writing default image"); + return true; + } private static final String CSS = "<style>\n" + @@ -492,6 +632,10 @@ public class ViewBlogServlet extends BaseServlet { " background-color: black;\n" + " color: white;\n" + "}\n" + +".syndieBlogLogo {\n" + +" float: left;\n" + +" display: inline;\n" + +"}\n" + ".syndieBlogLinks {\n" + " width: 200px;\n" + "}\n" + diff --git a/apps/syndie/jsp/images/default_blog_logo.png b/apps/syndie/jsp/images/default_blog_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..54ea2a91766e997c872302000f29c06e32bf64d5 Binary files /dev/null and b/apps/syndie/jsp/images/default_blog_logo.png differ diff --git a/history.txt b/history.txt index be1277259b51e2802072e0aeab2d9a5f5bcdd474..2f212f9d97a8311713f023b46b5e8598f4c22d41 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,12 @@ -$Id: history.txt,v 1.379 2006/01/08 15:54:39 jrandom Exp $ +$Id: history.txt,v 1.380 2006/01/09 01:33:29 jrandom Exp $ + +2005-01-09 jrandom + * Removed a longstanding bug that had caused unnecessary router identity + churn due to clock skew + * Temporarily sanity check within the streaming lib for long pending + writes + * Added support for a blog-wide logo to Syndie, and automated the pushing + of updated extended blog info data along side the metadata. 2005-01-09 jrandom * Bugfix for a rare SSU error (thanks cervantes!) diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 639c3d3fcb3d17718375d7bff848c4cb01a73c8e..a04172e69dc87a3635ea4f5edb29a2de645924ab 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.325 $ $Date: 2006/01/04 21:48:17 $"; + public final static String ID = "$Revision: 1.326 $ $Date: 2006/01/09 01:33:30 $"; public final static String VERSION = "0.6.1.8"; - public final static long BUILD = 9; + public final static long BUILD = 10; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java index 8501383c33d44609dd1b6810a4afda14ce443bb2..363c3aec226a42145468f96abbb7c10aa00bd0d0 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/KademliaNetworkDatabaseFacade.java @@ -303,8 +303,9 @@ public class KademliaNetworkDatabaseFacade extends NetworkDatabaseFacade { try { publish(ri); } catch (IllegalArgumentException iae) { - _log.log(Log.CRIT, "Our local router info is b0rked, clearing from scratch", iae); - _context.router().rebuildNewIdentity(); + _context.router().rebuildRouterInfo(true); + //_log.log(Log.CRIT, "Our local router info is b0rked, clearing from scratch", iae); + //_context.router().rebuildNewIdentity(); } }