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 6ad973cd06068f6a51df0416820ec36e66713c8e..41839f227a1261c4742720ec713bbd645e6a294a 100644 --- a/apps/syndie/java/src/net/i2p/syndie/data/BlogInfo.java +++ b/apps/syndie/java/src/net/i2p/syndie/data/BlogInfo.java @@ -57,6 +57,7 @@ public class BlogInfo { public static final String DESCRIPTION = "Description"; public static final String CONTACT_URL = "ContactURL"; public static final String EDITION = "Edition"; + public static final String SUMMARY_ENTRY_ID = "SummaryEntryId"; public void load(InputStream in) throws IOException { Log log = I2PAppContext.getGlobalContext().logManager().getLog(getClass()); diff --git a/apps/syndie/java/src/net/i2p/syndie/data/BlogInfoData.java b/apps/syndie/java/src/net/i2p/syndie/data/BlogInfoData.java new file mode 100644 index 0000000000000000000000000000000000000000..b1aeb60e01f387ef6e9b5218d3f53f08d6b4552c --- /dev/null +++ b/apps/syndie/java/src/net/i2p/syndie/data/BlogInfoData.java @@ -0,0 +1,127 @@ +package net.i2p.syndie.data; + +import java.io.*; +import java.util.*; +import net.i2p.client.naming.PetName; +import net.i2p.data.DataHelper; + +/** + * Contain the current supplementary data for rendering a blog, as opposed to + * just verifying and rendering a post. + */ +public class BlogInfoData { + private BlogURI _dataEntryId; + /** list of List of PetName instances that the blog refers to */ + private List _referenceGroups; + /** customized style config */ + private Properties _styleOverrides; + /** the blog's logo */ + private Attachment _logo; + private List _otherAttachments; + + public static final String ATTACHMENT_LOGO = "logo.png"; + public static final String ATTACHMENT_REFERENCE_GROUPS = "groups.txt"; + public static final String ATTACHMENT_STYLE_OVERRIDE = "style.cfg"; + /** identifies a post as being a blog info data, not a content bearing post */ + public static final String TAG = "BlogInfoData"; + + public BlogInfoData() {} + + public BlogURI getEntryId() { return _dataEntryId; } + public boolean isLogoSpecified() { return _logo != null; } + public Attachment getLogo() { return _logo; } + public boolean isStyleSpecified() { return _styleOverrides != null; } + public Properties getStyleOverrides() { return _styleOverrides; } + public int getReferenceGroupCount() { return _referenceGroups != null ? _referenceGroups.size() : 0; } + /** list of PetName elements to be included in the list */ + public List getReferenceGroup(int groupNum) { return (List)_referenceGroups.get(groupNum); } + public int getOtherAttachmentCount() { return _otherAttachments != null ? _otherAttachments.size() : 0; } + public Attachment getOtherAttachment(int num) { return (Attachment)_otherAttachments.get(num); } + public Attachment getOtherAttachment(String name) { + for (int i = 0; i < _otherAttachments.size(); i++) { + Attachment a = (Attachment)_otherAttachments.get(i); + if (a.getName().equals(name)) + return a; + } + return null; + } + + public void writeLogo(OutputStream out) throws IOException { + InputStream in = null; + try { + in = _logo.getDataStream(); + byte buf[] = new byte[4096]; + int read = 0; + while ( (read = in.read(buf)) != -1) + out.write(buf, 0, read); + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + } + + + public void load(EntryContainer entry) throws IOException { + _dataEntryId = entry.getURI(); + Attachment attachments[] = entry.getAttachments(); + for (int i = 0; i < attachments.length; i++) { + if (ATTACHMENT_LOGO.equals(attachments[i].getName())) { + _logo = attachments[i]; + } else if (ATTACHMENT_REFERENCE_GROUPS.equals(attachments[i].getName())) { + readReferenceGroups(attachments[i]); + } else if (ATTACHMENT_STYLE_OVERRIDE.equals(attachments[i].getName())) { + readStyleOverride(attachments[i]); + } else { + if (_otherAttachments == null) + _otherAttachments = new ArrayList(); + _otherAttachments.add(attachments[i]); + } + } + } + + private void readReferenceGroups(Attachment att) throws IOException { + InputStream in = null; + try { + in = att.getDataStream(); + StringBuffer line = new StringBuffer(128); + List groups = new ArrayList(); + String prevGroup = null; + List defaultGroup = new ArrayList(); + while (true) { + boolean ok = DataHelper.readLine(in, line); + if (line.length() > 0) { + PetName pn = new PetName(line.toString().trim()); + if (pn.getGroupCount() <= 0) { + defaultGroup.add(pn); + } else if (pn.getGroup(0).equals(prevGroup)) { + List curGroup = (List)groups.get(groups.size()-1); + curGroup.add(pn); + } else { + List curGroup = new ArrayList(); + curGroup.add(pn); + groups.add(curGroup); + prevGroup = pn.getGroup(0); + } + } + if (!ok) + break; + } + if (defaultGroup.size() > 0) + groups.add(defaultGroup); + _referenceGroups = groups; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + } + + private void readStyleOverride(Attachment att) throws IOException { + InputStream in = null; + try { + in = att.getDataStream(); + Properties props = new Properties(); + DataHelper.loadProps(props, in); + _styleOverrides = props; + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + } +} 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 d41a4500471e22f63438445d0ee550773bb65222..a03271e9ac019acc854eeb865077f6cccb0cf773 100644 --- a/apps/syndie/java/src/net/i2p/syndie/data/EntryContainer.java +++ b/apps/syndie/java/src/net/i2p/syndie/data/EntryContainer.java @@ -59,7 +59,10 @@ public class EntryContainer { public EntryContainer(BlogURI uri, String tags[], byte smlData[]) { this(); _entryURI = uri; - _entryData = new Entry(DataHelper.getUTF8(smlData)); + if ( (smlData == null) || (smlData.length <= 0) ) + _entryData = new Entry(null); + else + _entryData = new Entry(DataHelper.getUTF8(smlData)); setHeader(HEADER_BLOGKEY, Base64.encode(uri.getKeyHash().getData())); StringBuffer buf = new StringBuffer(); for (int i = 0; tags != null && i < tags.length; i++) @@ -203,10 +206,13 @@ public class EntryContainer { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ZipOutputStream out = new ZipOutputStream(baos); ZipEntry ze = new ZipEntry(ZIP_ENTRY); - byte data[] = DataHelper.getUTF8(_entryData.getText()); + byte data[] = null; + if (_entryData.getText() != null) + data = DataHelper.getUTF8(_entryData.getText()); ze.setTime(0); out.putNextEntry(ze); - out.write(data); + if (data != null) + out.write(data); out.closeEntry(); for (int i = 0; (_attachments != null) && (i < _attachments.length); i++) { ze = new ZipEntry(ZIP_ATTACHMENT_PREFIX + i + ZIP_ATTACHMENT_SUFFIX); @@ -270,6 +276,9 @@ public class EntryContainer { //System.out.println("Read entry [" + name + "] with size=" + entryData.length); } + if (_entryData == null) + _entryData = new Entry(null); + _attachments = new Attachment[attachments.size()]; for (int i = 0; i < attachments.size(); i++) { diff --git a/apps/syndie/java/src/net/i2p/syndie/sml/BlogRenderer.java b/apps/syndie/java/src/net/i2p/syndie/sml/BlogRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..c03c96d1b638b8c819319a63ba2bdb8cd18477dd --- /dev/null +++ b/apps/syndie/java/src/net/i2p/syndie/sml/BlogRenderer.java @@ -0,0 +1,189 @@ +package net.i2p.syndie.sml; + +import java.io.*; +import java.util.*; +import net.i2p.I2PAppContext; +import net.i2p.client.naming.PetName; +import net.i2p.data.*; +import net.i2p.syndie.data.*; +import net.i2p.syndie.web.*; + +/** + * Renders posts for display within the blog view + * + */ +public class BlogRenderer extends HTMLRenderer { + private BlogInfo _blog; + private BlogInfoData _data; + public BlogRenderer(I2PAppContext ctx, BlogInfo info, BlogInfoData data) { + super(ctx); + _blog = info; + _data = data; + } + + public void receiveHeaderEnd() { + _preBodyBuffer.append("<div class=\"syndieBlogPost\"><hr style=\"display: none\" />\n"); + _preBodyBuffer.append("<div class=\"syndieBlogPostHeader\">\n"); + _preBodyBuffer.append("<div class=\"syndieBlogPostSubject\">"); + String subject = (String)_headers.get(HEADER_SUBJECT); + if (subject == null) + subject = "[no subject]"; + String tags[] = _entry.getTags(); + for (int i = 0; (tags != null) && (i < tags.length); i++) + displayTag(_preBodyBuffer, _data, tags[i]); + _preBodyBuffer.append(getSpan("subjectText")).append(sanitizeString(subject)).append("</span></div>\n"); + + String name = getAuthor(); + String when = getEntryDate(_entry.getURI().getEntryId()); + _preBodyBuffer.append("<div class=\"syndieBlogPostFrom\">Posted by: <a href=\""); + _preBodyBuffer.append(getMetadataURL(_entry.getURI().getKeyHash())); + _preBodyBuffer.append("\" title=\"View their profile\">"); + _preBodyBuffer.append(sanitizeString(name)); + _preBodyBuffer.append("</a> on "); + _preBodyBuffer.append(when); + _preBodyBuffer.append("</div>\n"); + _preBodyBuffer.append("</div><!-- end syndieBlogPostHeader -->\n"); + + _preBodyBuffer.append("<div class=\"syndieBlogPostSummary\">\n"); + } + + public void receiveEnd() { + _postBodyBuffer.append("</div><!-- end syndieBlogPostSummary -->\n"); + _postBodyBuffer.append("<div class=\"syndieBlogPostDetails\">\n"); + int childCount = getChildCount(_archive.getIndex().getThreadedIndex().getNode(_entry.getURI())); + if ( (_cutReached || childCount > 0) && (_cutBody) ) { + _postBodyBuffer.append("<a href=\""); + _postBodyBuffer.append(getEntryURL()).append("\" title=\"View comments on this post\">Read more</a> "); + } + if (childCount > 0) { + _postBodyBuffer.append(childCount).append(" "); + if (childCount > 1) + _postBodyBuffer.append(" comments already, "); + else + _postBodyBuffer.append(" comment already, "); + } + _postBodyBuffer.append("<a href=\""); + _postBodyBuffer.append(getReplyURL()).append("\" title=\"Reply to this post\">Leave a comment</a>\n"); + _postBodyBuffer.append("</div><!-- end syndieBlogPostDetails -->\n"); + _postBodyBuffer.append("</div><!-- end syndieBlogPost -->\n\n"); + } + private int getChildCount(ThreadNode node) { + int nodes = 0; + for (int i = 0; i < node.getChildCount(); i++) { + nodes++; + nodes += getChildCount(node.getChild(i)); + } + return nodes; + } + + private String getAuthor() { + PetName pn = null; + if ( (_entry != null) && (_user != null) ) + pn = _user.getPetNameDB().getByLocation(_entry.getURI().getKeyHash().toBase64()); + if (pn != null) + return pn.getName(); + BlogInfo info = null; + if (_entry != null) { + info = _archive.getBlogInfo(_entry.getURI()); + if (info != null) { + String str = info.getProperty(BlogInfo.NAME); + if (str != null) + return str; + } + return _entry.getURI().getKeyHash().toBase64().substring(0,6); + } else { + return "No name?"; + } + } + + private void displayTag(StringBuffer buf, BlogInfoData data, String tag) { + //buf.append("<a href=\""); + //buf.append(getPageURL(_blog.getKey().calculateHash(), tag, -1, null, 5, 0, false, true)); + //buf.append("\" title=\"Filter the blog by the tag '").append(sanitizeTagParam(tag)).append("'\">"); + if ( (tag == null) || ("[none]".equals(tag) ) ) + return; + buf.append("<img src=\"").append(getTagIconURL(tag)).append("\" alt=\""); + buf.append(sanitizeTagParam(tag)).append("\" />"); + //buf.append("</a>"); + buf.append(" "); + } + + public String getMetadataURL(Hash blog) { return ThreadedHTMLRenderer.buildProfileURL(blog); } + private String getTagIconURL(String tag) { + return "viewicon.jsp?tag=" + Base64.encode(tag) + "&" + + ViewBlogServlet.PARAM_BLOG + "=" + _blog.getKey().calculateHash().toBase64(); + } + + private String getReplyURL() { + String subject = (String)_headers.get(HEADER_SUBJECT); + if (subject != null) { + if (!subject.startsWith("re:")) + subject = "re: " + subject; + } else { + subject = "re: "; + } + return "post.jsp?" + PostServlet.PARAM_PARENT + "=" + + Base64.encode(_entry.getURI().getKeyHash().toBase64() + "/" + _entry.getURI().getEntryId()) + "&" + + PostServlet.PARAM_SUBJECT + "=" + sanitizeTagParam(subject) + "&"; + } + + protected String getEntryURL() { return getEntryURL(_user != null ? _user.getShowImages() : true); } + protected String getEntryURL(boolean showImages) { + if (_entry == null) return "unknown"; + return "blog.jsp?" + + ViewBlogServlet.PARAM_BLOG + "=" + _blog.getKey().calculateHash().toBase64() + "&" + + ViewBlogServlet.PARAM_ENTRY + "=" + + Base64.encode(_entry.getURI().getKeyHash().getData()) + '/' + _entry.getURI().getEntryId(); + } + + protected String getAttachmentURLBase() { + return "invalid"; + } + + protected String getAttachmentURL(int id) { + if (_entry == null) return "unknown"; + return "blog.jsp?" + + ViewBlogServlet.PARAM_BLOG + "=" + _blog.getKey().calculateHash().toBase64() + "&" + + ViewBlogServlet.PARAM_ATTACHMENT + "=" + + Base64.encode(_entry.getURI().getKeyHash().getData()) + "/" + + _entry.getURI().getEntryId() + "/" + + id; + } + + public String getPageURL(String entry) { + StringBuffer buf = new StringBuffer(128); + buf.append("blog.jsp?"); + buf.append(ViewBlogServlet.PARAM_BLOG).append(_blog.getKey().calculateHash().toBase64()).append("&"); + + if (entry != null) { + if (entry.startsWith("entry://")) + entry = entry.substring("entry://".length()); + else if (entry.startsWith("blog://")) + entry = entry.substring("blog://".length()); + int split = entry.indexOf('/'); + if (split > 0) { + buf.append(ViewBlogServlet.PARAM_ENTRY).append("="); + buf.append(sanitizeTagParam(entry.substring(0, split))).append('/'); + buf.append(sanitizeTagParam(entry.substring(split+1))).append("&"); + } + } + return buf.toString(); + } + public String getPageURL(Hash blog, String tag, long entryId, String group, int numPerPage, int pageNum, boolean expandEntries, boolean showImages) { + StringBuffer buf = new StringBuffer(128); + buf.append("blog.jsp?"); + buf.append(ViewBlogServlet.PARAM_BLOG).append("="); + buf.append(_blog.getKey().calculateHash().toBase64()).append("&"); + + if ( (blog != null) && (entryId > 0) ) { + buf.append(ViewBlogServlet.PARAM_ENTRY).append("="); + buf.append(blog.toBase64()).append('/'); + buf.append(entryId).append("&"); + } + if (tag != null) + buf.append(ViewBlogServlet.PARAM_TAG).append('=').append(sanitizeTagParam(tag)).append("&"); + if ( (pageNum >= 0) && (numPerPage > 0) ) + buf.append(ViewBlogServlet.PARAM_OFFSET).append('=').append(pageNum*numPerPage).append("&"); + return buf.toString(); + } +} \ No newline at end of file diff --git a/apps/syndie/java/src/net/i2p/syndie/sml/EventReceiverImpl.java b/apps/syndie/java/src/net/i2p/syndie/sml/EventReceiverImpl.java index 399b01e921acca2c4731bb3f08c5aac14c7bf9d1..854882aa95c22098b97362c092ff1c0aa0315a19 100644 --- a/apps/syndie/java/src/net/i2p/syndie/sml/EventReceiverImpl.java +++ b/apps/syndie/java/src/net/i2p/syndie/sml/EventReceiverImpl.java @@ -8,7 +8,7 @@ import net.i2p.util.Log; * */ public class EventReceiverImpl implements SMLParser.EventReceiver { - private I2PAppContext _context; + protected I2PAppContext _context; private Log _log; public EventReceiverImpl(I2PAppContext ctx) { 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 16a10d62c9ac6b978e8c99f8ba717381e4cb7d17..3174f404fd4bef9133cbe5054189778efbf54940 100644 --- a/apps/syndie/java/src/net/i2p/syndie/sml/HTMLRenderer.java +++ b/apps/syndie/java/src/net/i2p/syndie/sml/HTMLRenderer.java @@ -1017,10 +1017,10 @@ public class HTMLRenderer extends EventReceiverImpl { _entry.getURI().getEntryId(); } - protected String getAttachmentURLBase() { return "viewattachment.jsp"; } + protected String getAttachmentURLBase() { return "viewattachment.jsp?"; } protected String getAttachmentURL(int id) { if (_entry == null) return "unknown"; - return getAttachmentURLBase() + "?" + + return getAttachmentURLBase() + ArchiveViewerBean.PARAM_BLOG + "=" + Base64.encode(_entry.getURI().getKeyHash().getData()) + "&" + ArchiveViewerBean.PARAM_ENTRY + "=" + _entry.getURI().getEntryId() + 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 66932e01bbb7951b8b07d1308be1d9e97ce358ad..19af7e9a4dcfce014b92ab290bf590a5bf6ae396 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/ArchiveViewerBean.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/ArchiveViewerBean.java @@ -596,9 +596,11 @@ public class ArchiveViewerBean { } public static void renderAttachment(Map parameters, OutputStream out) throws IOException { - Attachment a = getAttachment(parameters); + renderAttachment(getAttachment(parameters), out); + } + public static void renderAttachment(Attachment a, OutputStream out) throws IOException { if (a == null) { - renderInvalidAttachment(parameters, out); + renderInvalidAttachment(out); } else { InputStream data = a.getDataStream(); byte buf[] = new byte[1024]; @@ -610,17 +612,21 @@ public class ArchiveViewerBean { } public static final String getAttachmentContentType(Map parameters) { - Attachment a = getAttachment(parameters); - if (a == null) + return getAttachmentContentType(getAttachment(parameters)); + } + public static final String getAttachmentContentType(Attachment attachment) { + if (attachment == null) return "text/html"; - String mime = a.getMimeType(); + String mime = attachment.getMimeType(); if ( (mime != null) && ((mime.startsWith("image/") || mime.startsWith("text/plain"))) ) return mime; return "application/octet-stream"; } public static final boolean getAttachmentShouldShowInline(Map parameters) { - Attachment a = getAttachment(parameters); + return getAttachmentShouldShowInline(getAttachment(parameters)); + } + public static final boolean getAttachmentShouldShowInline(Attachment a) { if (a == null) return true; String mime = a.getMimeType(); @@ -631,7 +637,9 @@ public class ArchiveViewerBean { } public static final int getAttachmentContentLength(Map parameters) { - Attachment a = getAttachment(parameters); + return getAttachmentContentLength(getAttachment(parameters)); + } + public static final int getAttachmentContentLength(Attachment a) { if (a != null) return a.getDataLength(); else @@ -639,7 +647,9 @@ public class ArchiveViewerBean { } public static final String getAttachmentFilename(Map parameters) { - Attachment a = getAttachment(parameters); + return getAttachmentFilename(getAttachment(parameters)); + } + public static final String getAttachmentFilename(Attachment a) { if (a != null) return a.getName(); else @@ -667,7 +677,7 @@ public class ArchiveViewerBean { return null; } - private static void renderInvalidAttachment(Map parameters, OutputStream out) throws IOException { + private static void renderInvalidAttachment(OutputStream out) throws IOException { out.write(DataHelper.getUTF8("<span class=\"b_msgErr\">No such entry, or no such attachment</span>")); } diff --git a/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigBean.java b/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigBean.java new file mode 100644 index 0000000000000000000000000000000000000000..d5ecd1c2ddb5277e5ea00f97bfe668d0569339c9 --- /dev/null +++ b/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigBean.java @@ -0,0 +1,270 @@ +package net.i2p.syndie.web; + +import java.io.*; +import java.util.*; +import net.i2p.I2PAppContext; +import net.i2p.client.naming.PetName; +import net.i2p.data.DataHelper; +import net.i2p.syndie.*; +import net.i2p.syndie.data.*; +import net.i2p.util.Log; + +/** + * + */ +public class BlogConfigBean { + private I2PAppContext _context; + private Log _log; + private User _user; + private String _title; + private String _description; + private String _contactInfo; + /** list of list of PetNames */ + private List _groups; + private Properties _styleOverrides; + private File _logo; + private boolean _loaded; + private boolean _updated; + + public BlogConfigBean() { + _context = I2PAppContext.getGlobalContext(); + _log = _context.logManager().getLog(BlogConfigBean.class); + _groups = new ArrayList(); + _styleOverrides = new Properties(); + } + + public User getUser() { return _user; } + public void setUser(User user) { + _user = user; + _title = null; + _description = null; + _contactInfo = null; + _groups.clear(); + _styleOverrides.clear(); + if (_logo != null) + _logo.delete(); + _logo = null; + _loaded = false; + _updated = false; + load(); + } + public String getTitle() { return _title; } + public void setTitle(String title) { + _title = title; + _updated = true; + } + public String getDescription() { return _description; } + public void setDescription(String desc) { + _description = desc; + _updated = true; + } + public String getContactInfo() { return _contactInfo; } + public void setContactInfo(String info) { + _contactInfo = info; + _updated = true; + } + public int getGroupCount() { return _groups.size(); } + /** gets the actual modifiable list of PetName instances */ + public List getGroup(int i) { return (List)_groups.get(i); } + /** gets the actual modifiable list of PetName instances */ + public List getGroup(String name) { + for (int i = 0; i < _groups.size(); i++) { + List grp = (List)_groups.get(i); + if (grp.size() > 0) { + PetName pn = (PetName)grp.get(0); + if ( (pn.getGroupCount() == 0) && ( (name == null) || (name.length() <= 0) ) ) + return grp; + String curGroup = pn.getGroup(0); + if (curGroup.equals(name)) + return grp; + } + } + return null; + } + /** adds the given element to the appropriate group (creating a new one if necessary) */ + public void add(PetName pn) { + String groupName = null; + if (pn.getGroupCount() > 0) + groupName = pn.getGroup(0); + List group = getGroup(groupName); + if (group == null) { + group = new ArrayList(4); + group.add(pn); + _groups.add(group); + } else { + group.add(pn); + } + } + public void remove(PetName pn) { + String groupName = null; + if (pn.getGroupCount() > 0) + groupName = pn.getGroup(0); + List group = getGroup(groupName); + if (group != null) { + group.remove(pn); + if (group.size() <= 0) + _groups.remove(group); + } + } + public void remove(String name) { + for (int i = 0; i < getGroupCount(); i++) { + List group = getGroup(i); + for (int j = 0; j < group.size(); j++) { + PetName pn = (PetName)group.get(j); + if (pn.getName().equals(name)) { + group.remove(j); + if (group.size() <= 0) + _groups.remove(group); + return; + } + } + } + } + /** take note that the groups have been updated in some way (reordered, etc) */ + public void groupsUpdated() { _updated = true; } + public String getStyleOverride(String prop) { return _styleOverrides.getProperty(prop); } + public void setStyleOverride(String prop, String val) { + _styleOverrides.setProperty(prop, val); + _updated = true; + } + public void unsetStyleOverride(String prop) { + _styleOverrides.remove(prop); + _updated = true; + } + 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"); + return; + } + _logo = logo; + _updated = true; + } + public boolean hasPendingChanges() { return _updated; } + + private void load() { + Archive archive = BlogManager.instance().getArchive(); + BlogInfo info = archive.getBlogInfo(_user.getBlog()); + if (info != null) { + _title = info.getProperty(BlogInfo.NAME); + _description = info.getProperty(BlogInfo.DESCRIPTION); + _contactInfo = info.getProperty(BlogInfo.CONTACT_URL); + String id = info.getProperty(BlogInfo.SUMMARY_ENTRY_ID); + if (id != null) { + BlogURI uri = new BlogURI(id); + EntryContainer entry = archive.getEntry(uri); + if (entry != null) { + BlogInfoData data = new BlogInfoData(); + try { + data.load(entry); + if (data.isLogoSpecified()) { + File logo = File.createTempFile("logo", ".png", BlogManager.instance().getTempDir()); + FileOutputStream os = null; + try { + os = new FileOutputStream(logo); + data.writeLogo(os); + _logo = logo; + } finally { + if (os != null) try { os.close(); } catch (IOException ioe) {} + } + } + for (int i = 0; i < data.getReferenceGroupCount(); i++) { + List group = (List)data.getReferenceGroup(i); + for (int j = 0; j < group.size(); j++) { + PetName pn = (PetName)group.get(j); + add(pn); + } + } + _styleOverrides.putAll(data.getStyleOverrides()); + } catch (IOException ioe) { + _log.warn("Unable to load the blog info data from " + uri, ioe); + } + } + } + } + _loaded = true; + } + + public boolean publishChanges() throws IOException { + FileInputStream logo = null; + try { + if (_logo != null) + logo = new FileInputStream(_logo); + InputStream styleStream = createStyleStream(); + InputStream groupStream = createGroupStream(); + + String tags = BlogInfoData.TAG; + String subject = "n/a"; + String headers = ""; + String sml = ""; + List filenames = new ArrayList(); + List filestreams = new ArrayList(); + List filetypes = new ArrayList(); + if (logo != null) { + filenames.add(BlogInfoData.ATTACHMENT_LOGO); + filestreams.add(logo); + filetypes.add("image/png"); + } + filenames.add(BlogInfoData.ATTACHMENT_STYLE_OVERRIDE); + filestreams.add(styleStream); + filetypes.add("text/plain"); + filenames.add(BlogInfoData.ATTACHMENT_REFERENCE_GROUPS); + filestreams.add(groupStream); + filetypes.add("text/plain"); + + BlogURI uri = BlogManager.instance().createBlogEntry(_user, subject, tags, headers, sml, + filenames, filestreams, filetypes); + if (uri != null) { + Archive archive = BlogManager.instance().getArchive(); + BlogInfo info = archive.getBlogInfo(_user.getBlog()); + if (info != null) { + String props[] = info.getProperties(); + Properties opts = new Properties(); + for (int i = 0; i < props.length; i++) { + if (!props[i].equals(BlogInfo.SUMMARY_ENTRY_ID)) + opts.setProperty(props[i], info.getProperty(props[i])); + } + opts.setProperty(BlogInfo.SUMMARY_ENTRY_ID, uri.toString()); + boolean updated = BlogManager.instance().updateMetadata(_user, _user.getBlog(), opts); + if (updated) { + // ok great, published locally, though should we push it to others? + _log.info("Blog summary updated for " + _user + " in " + uri.toString()); + return true; + } + } else { + _log.error("Info is not known for " + _user.getBlog().toBase64()); + return false; + } + } else { + _log.error("Error creating the summary entry"); + return false; + } + } finally { + if (logo != null) try { logo.close(); } catch (IOException ioe) {} + // the other streams are in-memory, drop with the scope + } + return false; + } + private InputStream createStyleStream() throws IOException { + StringBuffer buf = new StringBuffer(1024); + if (_styleOverrides != null) { + for (Iterator iter = _styleOverrides.keySet().iterator(); iter.hasNext(); ) { + String key = (String)iter.next(); + String val = _styleOverrides.getProperty(key); + buf.append(key).append('=').append(val).append('\n'); + } + } + return new ByteArrayInputStream(DataHelper.getUTF8(buf)); + } + private InputStream createGroupStream() throws IOException { + StringBuffer buf = new StringBuffer(1024); + for (int i = 0; i < _groups.size(); i++) { + List group = (List)_groups.get(i); + for (int j = 0; j < group.size(); j++) { + PetName pn = (PetName)group.get(j); + buf.append(pn.toString()).append('\n'); + } + } + return new ByteArrayInputStream(DataHelper.getUTF8(buf)); + } +} diff --git a/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigServlet.java new file mode 100644 index 0000000000000000000000000000000000000000..117bf9161e32496a6816a2e4f4cfec671e1d5f26 --- /dev/null +++ b/apps/syndie/java/src/net/i2p/syndie/web/BlogConfigServlet.java @@ -0,0 +1,231 @@ +package net.i2p.syndie.web; + +import java.io.*; +import java.util.*; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletException; + +import net.i2p.I2PAppContext; +import net.i2p.client.naming.*; +import net.i2p.data.*; +import net.i2p.syndie.*; +import net.i2p.syndie.data.*; +import net.i2p.syndie.sml.*; + +/** + * Display our blog config, and let us edit it through several screens + * + */ +public class BlogConfigServlet extends BaseServlet { + private static final String ATTR_CONFIG_BEAN = "__blogConfigBean"; + public static final String PARAM_CONFIG_SCREEN = "screen"; + public static final String SCREEN_GENERAL = "general"; + public static final String SCREEN_REFERENCES = "references"; + 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; + } + BlogConfigBean bean = (BlogConfigBean)req.getSession().getAttribute(ATTR_CONFIG_BEAN); + if (bean == null) { + bean = new BlogConfigBean(); + bean.setUser(user); + } + + // handle actions here... + // on done handling + + String screen = req.getParameter(PARAM_CONFIG_SCREEN); + if (screen == null) + screen = SCREEN_GENERAL; + out.write("todo: Display screen " + screen); + /* + if (SCREEN_REFERENCES.equals(screen)) { + displayReferencesScreen(req, out, bean); + } else { + displayGeneralScreen(req, out, bean); + } + */ + } + /* + private void renderMyProfile(User user, String baseURI, PrintWriter out, Archive archive) throws IOException { + BlogInfo info = archive.getBlogInfo(user.getBlog()); + if (info == null) + return; + + out.write("<!-- " + info.toString() + "-->\n"); + out.write("<form action=\"" + baseURI + "\" method=\"POST\">\n"); + writeAuthActionFields(out); + // now add the form to update + out.write("<tr><td colspan=\"3\">Your profile</td></tr>\n"); + out.write("<tr><td colspan=\"3\">Name: <input type=\"text\" name=\"" + + ThreadedHTMLRenderer.PARAM_PROFILE_NAME + "\" value=\"" + + HTMLRenderer.sanitizeTagParam(info.getProperty(BlogInfo.NAME)) + "\"></td></tr>\n"); + out.write("<tr><td colspan=\"3\">Account description: <input type=\"text\" name=\"" + + ThreadedHTMLRenderer.PARAM_PROFILE_DESC + "\" value=\"" + + HTMLRenderer.sanitizeTagParam(info.getProperty(BlogInfo.DESCRIPTION)) + "\"></td></tr>\n"); + out.write("<tr><td colspan=\"3\">Contact information: <input type=\"text\" name=\"" + + ThreadedHTMLRenderer.PARAM_PROFILE_URL + "\" value=\"" + + HTMLRenderer.sanitizeTagParam(info.getProperty(BlogInfo.CONTACT_URL)) + "\"></td></tr>\n"); + out.write("<tr><td colspan=\"3\">Other attributes:<br /><textarea rows=\"3\" name=\"" + + ThreadedHTMLRenderer.PARAM_PROFILE_OTHER + "\" cols=\"60\">"); + String props[] = info.getProperties(); + if (props != null) { + for (int i = 0; i < props.length; i++) { + if (!BlogInfo.NAME.equals(props[i]) && + !BlogInfo.DESCRIPTION.equals(props[i]) && + !BlogInfo.EDITION.equals(props[i]) && + !BlogInfo.OWNER_KEY.equals(props[i]) && + !BlogInfo.POSTERS.equals(props[i]) && + !BlogInfo.SIGNATURE.equals(props[i]) && + !BlogInfo.CONTACT_URL.equals(props[i])) { + out.write(HTMLRenderer.sanitizeString(props[i], false) + ": " + + HTMLRenderer.sanitizeString(info.getProperty(props[i]), false) + "\n"); + } + } + } + out.write("</textarea></td></tr>\n"); + + if (user.getAuthenticated()) { + if ( (user.getUsername() == null) || (user.getUsername().equals(BlogManager.instance().getDefaultLogin())) ) { + // this is the default user, don't let them change the password + } else { + out.write("<tr><td colspan=\"3\">Old Password: <input type=\"password\" name=\"oldPassword\" /></td></tr>\n"); + out.write("<tr><td colspan=\"3\">Password: <input type=\"password\" name=\"password\" /></td></tr>\n"); + out.write("<tr><td colspan=\"3\">Password again: <input type=\"password\" name=\"passwordConfirm\" /></td></tr>\n"); + } + if (!BlogManager.instance().authorizeRemote(user)) { + out.write("<tr><td colspan=\"3\">To access the remote functionality, please specify the administrative password: <br />\n" + + "<input type=\"password\" name=\"adminPass\" /></td></tr>\n"); + } + } + + out.write("<tr><td colspan=\"3\"><input type=\"submit\" name=\"action\" value=\"Update profile\" /></td></tr>\n"); + out.write("</form>\n"); + } + + private void renderProfile(User user, String baseURI, PrintWriter out, Hash author, Archive archive) throws IOException { + out.write("<tr><td colspan=\"3\">Profile for "); + PetName pn = user.getPetNameDB().getByLocation(author.toBase64()); + String name = null; + BlogInfo info = archive.getBlogInfo(author); + if (pn != null) { + out.write(pn.getName()); + name = null; + if (info != null) + name = info.getProperty(BlogInfo.NAME); + + if ( (name == null) || (name.trim().length() <= 0) ) + name = author.toBase64().substring(0, 6); + + out.write(" (" + name + ")"); + } else { + if (info != null) + name = info.getProperty(BlogInfo.NAME); + + if ( (name == null) || (name.trim().length() <= 0) ) + name = author.toBase64().substring(0, 6); + out.write(name); + } + out.write("</a>"); + if (info != null) + out.write(" [edition " + info.getEdition() + "]"); + out.write("<br />\n"); + out.write("<a href=\"" + getControlTarget() + "?" + ThreadedHTMLRenderer.PARAM_AUTHOR + + '=' + author.toBase64() + "&" + ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR + "=true&\"" + + " title=\"View '" + HTMLRenderer.sanitizeTagParam(name) + "'s blog\">View their blog</a> or "); + out.write("<a href=\"" + getControlTarget() + "?" + ThreadedHTMLRenderer.PARAM_AUTHOR + + '=' + author.toBase64() + "&\">threads they have participated in</a>\n"); + out.write("</td></tr>\n"); + + out.write("<tr><td colspan=\"3\"><hr /></td></tr>\n"); + if (pn == null) { + out.write("<tr><td colspan=\"3\">Not currently bookmarked. Add them to your "); + String addFav = getAddToGroupLink(user, author, FilteredThreadIndex.GROUP_FAVORITE, + baseURI, "", "", "", "", "", author.toBase64()); + String addIgnore = getAddToGroupLink(user, author, FilteredThreadIndex.GROUP_IGNORE, + baseURI, "", "", "", "", "", author.toBase64()); + out.write("<a href=\"" + addFav + "\" title=\"Threads by favorite authors are shown specially\">favorites</a> or "); + out.write("<a href=\"" + addIgnore + "\" title=\"Threads by ignored authors are hidden from view\">ignored</a> "); + out.write("</td></tr>\n"); + } else if (pn.isMember(FilteredThreadIndex.GROUP_IGNORE)) { + out.write("<tr><td colspan=\"3\">Currently ignored - threads they create are hidden.</td></tr>\n"); + String remIgnore = getRemoveFromGroupLink(user, pn.getName(), FilteredThreadIndex.GROUP_IGNORE, + baseURI, "", "", "", "", "", author.toBase64()); + out.write("<tr><td colspan=\"3\"><a href=\"" + remIgnore + "\">Unignore " + pn.getName() + "</a></td></tr>\n"); + String remCompletely = getRemoveFromGroupLink(user, pn.getName(), "", + baseURI, "", "", "", "", "", author.toBase64()); + out.write("<tr><td colspan=\"3\"><a href=\"" + remCompletely + "\">Forget about " + pn.getName() + " entirely</a></td></tr>\n"); + } else if (pn.isMember(FilteredThreadIndex.GROUP_FAVORITE)) { + out.write("<tr><td colspan=\"3\">Currently marked as a favorite author - threads they participate in " + + "are highlighted.</td></tr>\n"); + String remIgnore = getRemoveFromGroupLink(user, pn.getName(), FilteredThreadIndex.GROUP_FAVORITE, + baseURI, "", "", "", "", "", author.toBase64()); + out.write("<tr><td colspan=\"3\"><a href=\"" + remIgnore + "\">Remove " + pn.getName() + " from the list of favorite authors</a></td></tr>\n"); + String addIgnore = getAddToGroupLink(user, author, FilteredThreadIndex.GROUP_IGNORE, + baseURI, "", "", "", "", "", author.toBase64()); + out.write("<tr><td colspan=\"3\"><a href=\"" + addIgnore + "\" title=\"Threads by ignored authors are hidden from view\">Ignore the author</a></td></tr>"); + String remCompletely = getRemoveFromGroupLink(user, pn.getName(), "", + baseURI, "", "", "", "", "", author.toBase64()); + out.write("<tr><td colspan=\"3\"><a href=\"" + remCompletely + "\">Forget about " + pn.getName() + " entirely</a></td></tr>\n"); + } else { + out.write("<tr><td colspan=\"3\">Currently bookmarked. Add them to your "); + String addFav = getAddToGroupLink(user, author, FilteredThreadIndex.GROUP_FAVORITE, + baseURI, "", "", "", "", "", author.toBase64()); + String addIgnore = getAddToGroupLink(user, author, FilteredThreadIndex.GROUP_IGNORE, + baseURI, "", "", "", "", "", author.toBase64()); + out.write("<a href=\"" + addFav + "\" title=\"Threads by favorite authors are shown specially\">favorites</a> or "); + out.write("<a href=\"" + addIgnore + "\" title=\"Threads by ignored authors are hidden from view\">ignored</a> list</td></tr>"); + String remCompletely = getRemoveFromGroupLink(user, pn.getName(), "", + baseURI, "", "", "", "", "", author.toBase64()); + out.write("<tr><td colspan=\"3\"><a href=\"" + remCompletely + "\">Forget about " + pn.getName() + " entirely</a></td></tr>\n"); + } + + if (info != null) { + String descr = info.getProperty(BlogInfo.DESCRIPTION); + if ( (descr != null) && (descr.trim().length() > 0) ) + out.write("<tr><td colspan=\"3\">Account description: " + HTMLRenderer.sanitizeString(descr) + "</td></tr>\n"); + + String contactURL = info.getProperty(BlogInfo.CONTACT_URL); + if ( (contactURL != null) && (contactURL.trim().length() > 0) ) + out.write("<tr><td colspan=\"3\">Contact information: " + + HTMLRenderer.sanitizeString(contactURL) + "</td></tr>\n"); + + String props[] = info.getProperties(); + int altCount = 0; + if (props != null) + for (int i = 0; i < props.length; i++) + if (!BlogInfo.NAME.equals(props[i]) && + !BlogInfo.DESCRIPTION.equals(props[i]) && + !BlogInfo.EDITION.equals(props[i]) && + !BlogInfo.OWNER_KEY.equals(props[i]) && + !BlogInfo.POSTERS.equals(props[i]) && + !BlogInfo.SIGNATURE.equals(props[i]) && + !BlogInfo.CONTACT_URL.equals(props[i])) + altCount++; + if (altCount > 0) { + for (int i = 0; i < props.length; i++) { + if (!BlogInfo.NAME.equals(props[i]) && + !BlogInfo.DESCRIPTION.equals(props[i]) && + !BlogInfo.EDITION.equals(props[i]) && + !BlogInfo.OWNER_KEY.equals(props[i]) && + !BlogInfo.POSTERS.equals(props[i]) && + !BlogInfo.SIGNATURE.equals(props[i]) && + !BlogInfo.CONTACT_URL.equals(props[i])) { + out.write("<tr><td colspan=\"3\">"); + out.write(HTMLRenderer.sanitizeString(props[i]) + ": " + + HTMLRenderer.sanitizeString(info.getProperty(props[i]))); + out.write("</td></tr>\n"); + } + } + } + } + } + */ + + protected String getTitle() { return "Syndie :: Configure blog"; } +} 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 67fe19171b910ac32b9a8dddb5993575d605969c..6fd9133c47aee6c98ff0166e982b16c05212c19b 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/ProfileServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/ProfileServlet.java @@ -59,7 +59,7 @@ public class ProfileServlet extends BaseServlet { out.write("<form action=\"" + baseURI + "\" method=\"POST\">\n"); writeAuthActionFields(out); // now add the form to update - out.write("<tr><td colspan=\"3\">Your profile</td></tr>\n"); + out.write("<tr><td colspan=\"3\">Your profile (<a href=\"configblog.jsp\">configure your blog</a>)</td></tr>\n"); out.write("<tr><td colspan=\"3\">Name: <input type=\"text\" name=\"" + ThreadedHTMLRenderer.PARAM_PROFILE_NAME + "\" value=\"" + HTMLRenderer.sanitizeTagParam(info.getProperty(BlogInfo.NAME)) + "\"></td></tr>\n"); diff --git a/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogServlet.java new file mode 100644 index 0000000000000000000000000000000000000000..5d5d780c79544af52b248c46f0c33c0ee46aebf9 --- /dev/null +++ b/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogServlet.java @@ -0,0 +1,478 @@ +package net.i2p.syndie.web; + +import java.io.*; +import java.util.*; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import net.i2p.I2PAppContext; +import net.i2p.client.naming.*; +import net.i2p.data.*; +import net.i2p.syndie.*; +import net.i2p.syndie.data.*; +import net.i2p.syndie.sml.*; +import net.i2p.util.FileUtil; +import net.i2p.util.Log; + +/** + * Render the appropriate posts for the current blog, using any blog info data available + * + */ +public class ViewBlogServlet extends BaseServlet { + public static final String PARAM_OFFSET = "offset"; + /** $blogHash */ + public static final String PARAM_BLOG = "blog"; + /** $blogHash/$entryId */ + public static final String PARAM_ENTRY = "entry"; + /** tag,tag,tag */ + public static final String PARAM_TAG = "tag"; + /** $blogHash/$entryId/$attachmentId */ + public static final String PARAM_ATTACHMENT = "attachment"; + + public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + req.setCharacterEncoding("UTF-8"); + String attachment = req.getParameter(PARAM_ATTACHMENT); + if (attachment != null) { + // if they requested an attachment, serve it up to 'em + if (renderAttachment(req, resp, attachment)) + return; + } + //todo: take care of logo requests, etc + super.service(req, resp); + } + + protected void render(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index) throws ServletException, IOException { + Archive archive = BlogManager.instance().getArchive(); + + Hash blog = null; + String name = req.getParameter(PARAM_BLOG); + if ( (name == null) || (name.trim().length() <= 0) ) { + blog = user.getBlog(); + } else { + byte val[] = Base64.decode(name); + if ( (val != null) && (val.length == Hash.HASH_LENGTH) ) + blog = new Hash(val); + } + + BlogInfo info = null; + if (blog != null) + info = archive.getBlogInfo(blog); + + int offset = 0; + String off = req.getParameter(PARAM_OFFSET); + if (off != null) try { offset = Integer.parseInt(off); } catch (NumberFormatException nfe) {} + + List posts = getPosts(user, archive, info, req, index); + render(user, req, out, archive, info, posts, offset); + } + + private BlogURI getEntry(HttpServletRequest req) { + String param = req.getParameter(PARAM_ENTRY); + if (param != null) + return new BlogURI("blog://" + param); + return null; + } + + private List getPosts(User user, Archive archive, BlogInfo info, HttpServletRequest req, ThreadIndex index) { + List rv = new ArrayList(1); + if (info == null) return rv; + + ArchiveIndex aindex = archive.getIndex(); + + BlogURI uri = getEntry(req); + if (uri != null) { + rv.add(uri); + return rv; + } + + aindex.selectMatchesOrderByEntryId(rv, info.getKey().calculateHash(), null); + + // lets filter out any posts that are not roots + for (int i = 0; i < rv.size(); i++) { + BlogURI curURI = (BlogURI)rv.get(i); + ThreadNode node = index.getNode(curURI); + if ( (node != null) && (node.getParent() == null) ) { + // ok, its a root + Collection tags = node.getTags(); + if ( (tags != null) && (tags.contains(BlogInfoData.TAG)) ) { + // skip this, as its an info post + rv.remove(i); + i--; + } + } else { + rv.remove(i); + i--; + } + } + return rv; + } + + private void render(User user, HttpServletRequest req, PrintWriter out, Archive archive, BlogInfo info, List posts, int offset) throws IOException { + String title = null; + String desc = null; + BlogInfoData data = null; + if (info != null) { + title = info.getProperty(BlogInfo.NAME); + desc = info.getProperty(BlogInfo.DESCRIPTION); + String dataURI = info.getProperty(BlogInfo.SUMMARY_ENTRY_ID); + if (dataURI != null) { + EntryContainer entry = archive.getEntry(new BlogURI(dataURI)); + if (entry != null) { + data = new BlogInfoData(); + try { + data.load(entry); + } catch (IOException ioe) { + data = null; + if (_log.shouldLog(Log.WARN)) + _log.warn("Error loading the blog info data from " + dataURI, ioe); + } + } + } + } + String pageTitle = "Syndie :: Blogs" + (desc != null ? " :: " + desc : ""); + if (title != null) pageTitle = pageTitle + " (" + title + ")"; + pageTitle = HTMLRenderer.sanitizeString(pageTitle); + out.write("<html>\n<head>\n<title>" + pageTitle + "</title>\n"); + out.write("<style>"); + renderStyle(out, info, data, req); + out.write("</style></head>"); + renderHeader(user, req, out, info, data, title, desc); + renderReferences(out, info, data); + renderBody(user, out, info, data, posts, offset, archive, req); + out.write("</body></html>\n"); + } + private void renderStyle(PrintWriter out, BlogInfo info, BlogInfoData data, HttpServletRequest req) throws IOException { + // modify it based on data.getStyleOverrides()... + out.write(CSS); + Reader css = null; + try { + InputStream in = req.getSession().getServletContext().getResourceAsStream("/syndie.css"); + if (in != null) { + css = new InputStreamReader(in, "UTF-8"); + char buf[] = new char[1024]; + int read = 0; + while ( (read = css.read(buf)) != -1) + out.write(buf, 0, read); + } + } finally { + if (css != null) + css.close(); + } + String content = FileUtil.readTextFile("./docs/syndie_standard.css", -1, true); + if (content != null) out.write(content); + } + + 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"); + } + } + String name = desc; + if ( (name == null) || (name.trim().length() <= 0) ) + name = title; + if ( ( (name == null) || (name.trim().length() <= 0) ) && (info != null) ) + name = info.getKey().calculateHash().toBase64(); + if (name != null) { + String url = "blog.jsp?" + (info != null ? PARAM_BLOG + "=" + info.getKey().calculateHash().toBase64() : ""); + out.write("<b><a href=\"" + url + "\" title=\"Go to the blog root\">" + + HTMLRenderer.sanitizeString(name) + "</a></b>"); + } + out.write("</div>\n"); + } + + private static final String DEFAULT_GROUP_NAME = "References"; + private void renderReferences(PrintWriter out, BlogInfo info, BlogInfoData data) throws IOException { + out.write("<div class=\"syndieBlogLinks\">\n"); + if (data != null) { + for (int i = 0; i < data.getReferenceGroupCount(); i++) { + List group = data.getReferenceGroup(i); + if (group.size() <= 0) continue; + PetName pn = (PetName)group.get(0); + String name = null; + if (pn.getGroupCount() <= 0) + name = DEFAULT_GROUP_NAME; + else + name = HTMLRenderer.sanitizeString(pn.getGroup(0)); + out.write("<!-- group " + name + " -->\n"); + out.write("<div class=\"syndieBlogLinkGroup\">\n"); + out.write("<span class=\"syndieBlogLinkGroupName\">" + name + "</span>\n"); + out.write("<ul>\n"); + for (int j = 0; j < group.size(); j++) { + pn = (PetName)group.get(j); + out.write("<li>" + renderLink(pn) + "</li>\n"); + } + out.write("</ul>\n</div>\n<!-- end " + name + " -->\n"); + } + } + out.write("<div class=\"syndieBlogLinkGroup\">\n"); + out.write("<span class=\"syndieBlogLinkGroupName\">Custom links</span>\n"); + out.write("<ul><li><a href=\"\">are not yet implemented</a></li><li><a href=\"\">but are coming soon</a></li></ul>\n"); + out.write("</div><!-- end fake group -->"); + out.write("<div class=\"syndieBlogMeta\">"); + out.write("Secured by <a href=\"http://syndie.i2p.net/\">Syndie</a>"); + out.write("</div>\n"); + out.write("</div><!-- end syndieBlogLinks -->\n\n"); + } + + private String renderLink(PetName pn) { + return "<a href=\"\" title=\"go somewhere\">" + HTMLRenderer.sanitizeString(pn.getName()) + "</a>"; + } + + private static final int POSTS_PER_PAGE = 5; + private void renderBody(User user, PrintWriter out, BlogInfo info, BlogInfoData data, List posts, int offset, Archive archive, HttpServletRequest req) throws IOException { + out.write("<div class=\"syndieBlogBody\">\n<span style=\"display: none\" id=\"content\"></span>\n\n"); + if (info == null) { + out.write("No blog specified\n"); + return; + } + + BlogRenderer renderer = new BlogRenderer(_context, info, data); + + if ( (posts.size() == 1) && (req.getParameter(PARAM_ENTRY) != null) ) { + BlogURI uri = (BlogURI)posts.get(0); + EntryContainer entry = archive.getEntry(uri); + renderer.render(user, archive, entry, out, false, true); + renderComments(user, out, info, data, entry, archive, renderer); + } else { + for (int i = offset; i < posts.size() && i < offset + POSTS_PER_PAGE; i++) { + BlogURI uri = (BlogURI)posts.get(i); + EntryContainer entry = archive.getEntry(uri); + renderer.render(user, archive, entry, out, true, true); + } + + renderNav(out, info, data, posts, offset, archive, req); + } + + out.write("</div><!-- end syndieBlogBody -->\n"); + } + + private void renderComments(User user, PrintWriter out, BlogInfo info, BlogInfoData data, EntryContainer entry, + Archive archive, BlogRenderer renderer) throws IOException { + ArchiveIndex index = archive.getIndex(); + out.write("<div class=\"syndieBlogComments\">\n"); + renderComments(user, out, entry.getURI(), archive, index, renderer); + out.write("</div>\n"); + } + private void renderComments(User user, PrintWriter out, BlogURI parentURI, Archive archive, ArchiveIndex index, BlogRenderer renderer) throws IOException { + List replies = index.getReplies(parentURI); + if (replies.size() > 0) { + out.write("<ul>\n"); + for (int i = 0; i < replies.size(); i++) { + BlogURI uri = (BlogURI)replies.get(i); + out.write("<li>"); + if (!shouldIgnore(user, uri)) { + EntryContainer cur = archive.getEntry(uri); + renderer.render(user, archive, cur, out, false, true); + // recurse + renderComments(user, out, uri, archive, index, renderer); + } + out.write("</li>\n"); + } + out.write("</ul>\n"); + } + } + + private boolean shouldIgnore(User user, BlogURI uri) { + PetName pn = user.getPetNameDB().getByLocation(uri.getKeyHash().toBase64()); + return ( (pn != null) && pn.isMember(FilteredThreadIndex.GROUP_IGNORE)); + } + + private void renderNav(PrintWriter out, BlogInfo info, BlogInfoData data, List posts, int offset, Archive archive, HttpServletRequest req) throws IOException { + out.write("<div class=\"syndieBlogNav\"><hr style=\"display: none\" />\n"); + String uri = req.getRequestURI() + "?"; + if (info != null) + uri = uri + PARAM_BLOG + "=" + info.getKey().calculateHash().toBase64() + "&"; + if (offset + POSTS_PER_PAGE >= posts.size()) + out.write(POSTS_PER_PAGE + " more older entries"); + else + out.write("<a href=\"" + uri + "offset=" + (offset+POSTS_PER_PAGE) + "\">" + + POSTS_PER_PAGE + " older entries</a>"); + out.write(" | "); + if (offset <= 0) + out.write(POSTS_PER_PAGE + " more recent entries"); + else + out.write("<a href=\"" + uri + "offset=" + + (offset >= POSTS_PER_PAGE ? offset-POSTS_PER_PAGE : 0) + + "\">" + POSTS_PER_PAGE + " more recent entries</a>"); + + out.write("</div><!-- end syndieBlogNav -->\n"); + } + + /** + * render the attachment to the browser, using the appropriate mime types, etc + * @param attachment formatted as $blogHash/$entryId/$attachmentId + * @return true if rendered + */ + private boolean renderAttachment(HttpServletRequest req, HttpServletResponse resp, String attachment) throws ServletException, IOException { + int split = attachment.lastIndexOf('/'); + if (split <= 0) + return false; + BlogURI uri = new BlogURI("blog://" + attachment.substring(0, split)); + try { + int attachmentId = Integer.parseInt(attachment.substring(split+1)); + if (attachmentId < 0) return false; + EntryContainer entry = BlogManager.instance().getArchive().getEntry(uri); + if (entry == null) { + System.out.println("Could not render the attachment [" + uri + "] / " + attachmentId); + return false; + } + Attachment attachments[] = entry.getAttachments(); + if (attachmentId >= attachments.length) { + System.out.println("Out of range attachment on " + uri + ": " + attachmentId); + return false; + } + + resp.setContentType(ArchiveViewerBean.getAttachmentContentType(attachments[attachmentId])); + boolean inline = ArchiveViewerBean.getAttachmentShouldShowInline(attachments[attachmentId]); + String filename = ArchiveViewerBean.getAttachmentFilename(attachments[attachmentId]); + if (inline) + resp.setHeader("Content-Disposition", "inline; filename=\"" + filename + "\""); + else + resp.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\""); + int len = ArchiveViewerBean.getAttachmentContentLength(attachments[attachmentId]); + if (len >= 0) + resp.setContentLength(len); + ArchiveViewerBean.renderAttachment(attachments[attachmentId], resp.getOutputStream()); + return true; + } catch (NumberFormatException nfe) {} + return false; + } + + private static final String CSS = +"<style>\n" + +"body {\n" + +" margin: 0px;\n" + +" padding: 0px;\n" + +" font-family: Arial, Helvetica, sans-serif;\n" + +"}\n" + +".syndieBlog {\n" + +" font-size: 100%;\n" + +" margin: 0px;\n" + +" border: 0px;\n" + +" padding: 0px;\n" + +" border-width: 0px;\n" + +" border-spacing: 0px;\n" + +"}\n" + +".syndieBlogTopNav {\n" + +" width: 100%;\n" + +" height: 20px;\n" + +" background-color: #BBBBBB;\n" + +"}\n" + +".syndieBlogTopNavUser {\n" + +" text-align: left;\n" + +" float: left;\n" + +" display: inline;\n" + +"}\n" + +".syndieBlogTopNavAdmin {\n" + +" text-align: left;\n" + +" float: right;\n" + +" display: inline;\n" + +"}\n" + +".syndieBlogHeader {\n" + +" width: 100%;\n" + +" height: 50px;\n" + +" font-size: 120%;\n" + +" background-color: black;\n" + +" color: white;\n" + +"}\n" + +".syndieBlogLinks {\n" + +" width: 200px;\n" + +"}\n" + +".syndieBlogLinkGroup {\n" + +" text-align: left;\n" + +" font-size: 80%;\n" + +" background-color: #DDD;\n" + +" border: solid;\n" + +" //border-width: 5px 5px 0px 5px;\n" + +" //border-color: #FFFFFF;\n" + +" border-width: 1px 1px 1px 1px;\n" + +" border-color: #000;\n" + +" margin-top: 5px;\n" + +" margin-right: 5px;\n" + +"}\n" + +".syndieBlogLinkGroup ul {\n" + +" list-style: none;\n" + +" margin-left: 0;\n" + +" margin-top: 0;\n" + +" margin-bottom: 0;\n" + +" padding-left: 0;\n" + +"}\n" + +".syndieBlogLinkGroup li {\n" + +" margin: 0;\n" + +"}\n" + +".syndieBlogLinkGroup li a {\n" + +" display: block;\n" + +" width: 100%;\n" + +"}\n" + +".syndieBlogLinkGroupName {\n" + +" font-size: 80%;\n" + +" font-weight: bold;\n" + +"}\n" + +".syndieBlogMeta {\n" + +" text-align: left;\n" + +" font-size: 80%;\n" + +" background-color: #DDD;\n" + +" border: solid;\n" + +" border-width: 1px 1px 1px 1px;\n" + +" border-color: #000;\n" + +" width: 90%;\n" + +" margin-top: 5px;\n" + +" margin-right: 5px;\n" + +"}\n" + +".syndieBlogBody {\n" + +" position: absolute;\n" + +" top: 70px;\n" + +" left: 200px;\n" + +" float: left;\n" + +"}\n" + +".syndieBlogPost {\n" + +" border: solid;\n" + +" border-width: 1px 1px 1px 1px;\n" + +" border-color: #000;\n" + +" margin-top: 5px;\n" + +" width: 100%;\n" + +"}\n" + +".syndieBlogPostHeader {\n" + +" background-color: #BBB;\n" + +"}\n" + +".syndieBlogPostSubject {\n" + +" text-align: left;\n" + +"}\n" + +".syndieBlogPostFrom {\n" + +" text-align: right;\n" + +"}\n" + +".syndieBlogPostSummary {\n" + +" background-color: #FFFFFF;\n" + +"}\n" + +".syndieBlogPostDetails {\n" + +" background-color: #DDD;\n" + +"}\n" + +".syndieBlogNav {\n" + +" text-align: center;\n" + +"}\n" + +".syndieBlogComments {\n" + +" border: none;\n" + +" margin-top: 5px;\n" + +" margin-left: 0px;\n" + +" float: left;\n" + +"}\n" + +".syndieBlogComments ul {\n" + +" list-style: none;\n" + +" margin-left: 10;\n" + +" padding-left: 0;\n" + +"}\n"; + + protected String getTitle() { return "unused"; } + protected void renderServletDetails(User user, HttpServletRequest req, PrintWriter out, ThreadIndex index, + int threadOffset, BlogURI visibleEntry, Archive archive) throws IOException { + throw new RuntimeException("unused"); + } +} diff --git a/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogsServlet.java b/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogsServlet.java index 6f09e4153dfee523bd243ac2d5fad166ba176a19..80f28402dc672b0d797e0fd23d6ac2075047f2d9 100644 --- a/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogsServlet.java +++ b/apps/syndie/java/src/net/i2p/syndie/web/ViewBlogsServlet.java @@ -29,8 +29,9 @@ public class ViewBlogsServlet extends BaseServlet { if ( (lastPost > 0) && (dayBegin - 3*24*60*60*1000l >= lastPost) ) // last post was old 3 days ago daysAgo = (int)((dayBegin - lastPost + 24*60*60*1000l-1)/(24*60*60*1000l)); daysAgo++; - return getControlTarget() + "?" + ThreadedHTMLRenderer.PARAM_AUTHOR + '=' + blog.toBase64() - + '&' + ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR + "=true&daysBack=" + daysAgo; + return "blog.jsp?" + ViewBlogServlet.PARAM_BLOG + "=" + blog.toBase64(); + //return getControlTarget() + "?" + ThreadedHTMLRenderer.PARAM_AUTHOR + '=' + blog.toBase64() + // + '&' + ThreadedHTMLRenderer.PARAM_THREAD_AUTHOR + "=true&daysBack=" + daysAgo; } private String getPostDate(long when) { diff --git a/apps/syndie/jsp/web.xml b/apps/syndie/jsp/web.xml index 6a57e60a383d2684fa15ec9278c3a7f1796277e1..3da3b8bd36702581d29bb73651a4f9cbdf6190df 100644 --- a/apps/syndie/jsp/web.xml +++ b/apps/syndie/jsp/web.xml @@ -69,6 +69,16 @@ <servlet-class>net.i2p.syndie.web.ViewBlogsServlet</servlet-class> </servlet> + <servlet> + <servlet-name>net.i2p.syndie.web.BlogConfigServlet</servlet-name> + <servlet-class>net.i2p.syndie.web.BlogConfigServlet</servlet-class> + </servlet> + + <servlet> + <servlet-name>net.i2p.syndie.web.ViewBlogServlet</servlet-name> + <servlet-class>net.i2p.syndie.web.ViewBlogServlet</servlet-class> + </servlet> + <servlet> <servlet-name>net.i2p.syndie.UpdaterServlet</servlet-name> <servlet-class>net.i2p.syndie.UpdaterServlet</servlet-class> @@ -135,6 +145,14 @@ <servlet-name>net.i2p.syndie.web.ViewBlogsServlet</servlet-name> <url-pattern>/blogs.jsp</url-pattern> </servlet-mapping> + <servlet-mapping> + <servlet-name>net.i2p.syndie.web.BlogConfigServlet</servlet-name> + <url-pattern>/configblog.jsp</url-pattern> + </servlet-mapping> + <servlet-mapping> + <servlet-name>net.i2p.syndie.web.ViewBlogServlet</servlet-name> + <url-pattern>/blog.jsp</url-pattern> + </servlet-mapping> <session-config> <session-timeout> diff --git a/history.txt b/history.txt index 4b43e3ceb068b3d7f12080f7e6ff04bc42f6909b..c2d67c55320580eb79c61e7d6ff3bad7acacb100 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,8 @@ -$Id: history.txt,v 1.377 2006/01/01 12:23:29 jrandom Exp $ +$Id: history.txt,v 1.378 2006/01/04 21:48:17 jrandom Exp $ + +2006-01-08 jrandom + * First pass of the new blog interface, though without much of the useful + customization features (coming soon) 2006-01-04 jrandom * Rather than profile individual tunnels for throughput over their