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 6ad973cd06..41839f227a 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 0000000000..b1aeb60e01 --- /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 d41a450047..a03271e9ac 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 0000000000..c03c96d1b6 --- /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("

\n"); + _preBodyBuffer.append("
\n"); + _preBodyBuffer.append("
"); + 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("
\n"); + + String name = getAuthor(); + String when = getEntryDate(_entry.getURI().getEntryId()); + _preBodyBuffer.append("
Posted by: "); + _preBodyBuffer.append(sanitizeString(name)); + _preBodyBuffer.append(" on "); + _preBodyBuffer.append(when); + _preBodyBuffer.append("
\n"); + _preBodyBuffer.append("
\n"); + + _preBodyBuffer.append("
\n"); + } + + public void receiveEnd() { + _postBodyBuffer.append("
\n"); + _postBodyBuffer.append("
\n"); + int childCount = getChildCount(_archive.getIndex().getThreadedIndex().getNode(_entry.getURI())); + if ( (_cutReached || childCount > 0) && (_cutBody) ) { + _postBodyBuffer.append("Read more "); + } + if (childCount > 0) { + _postBodyBuffer.append(childCount).append(" "); + if (childCount > 1) + _postBodyBuffer.append(" comments already, "); + else + _postBodyBuffer.append(" comment already, "); + } + _postBodyBuffer.append("Leave a comment\n"); + _postBodyBuffer.append("
\n"); + _postBodyBuffer.append("
\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(""); + if ( (tag == null) || ("[none]".equals(tag) ) ) + return; + buf.append("\"");"); + //buf.append(""); + 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 399b01e921..854882aa95 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 16a10d62c9..3174f404fd 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 66932e01bb..19af7e9a4d 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("No such entry, or no such attachment")); } 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 0000000000..d5ecd1c2dd --- /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 0000000000..117bf9161e --- /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("\n"); + out.write("
\n"); + writeAuthActionFields(out); + // now add the form to update + out.write("Your profile\n"); + out.write("Name: \n"); + out.write("Account description: \n"); + out.write("Contact information: \n"); + out.write("Other attributes:
\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("Old Password: \n"); + out.write("Password: \n"); + out.write("Password again: \n"); + } + if (!BlogManager.instance().authorizeRemote(user)) { + out.write("To access the remote functionality, please specify the administrative password:
\n" + + "\n"); + } + } + + out.write("\n"); + out.write("
\n"); + } + + private void renderProfile(User user, String baseURI, PrintWriter out, Hash author, Archive archive) throws IOException { + out.write("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(""); + if (info != null) + out.write(" [edition " + info.getEdition() + "]"); + out.write("
\n"); + out.write("View their blog or "); + out.write("\n\n" + pageTitle + "\n"); + out.write(""); + renderHeader(user, req, out, info, data, title, desc); + renderReferences(out, info, data); + renderBody(user, out, info, data, posts, offset, archive, req); + out.write("\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("\n" + + "Content\n"); + renderNavBar(user, req, out); + out.write("
\n"); + if (data != null) { + if (data.isLogoSpecified()) { + out.write("\"\"\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("" + + HTMLRenderer.sanitizeString(name) + ""); + } + out.write("
\n"); + } + + private static final String DEFAULT_GROUP_NAME = "References"; + private void renderReferences(PrintWriter out, BlogInfo info, BlogInfoData data) throws IOException { + out.write("
\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("\n"); + out.write("
\n"); + out.write("" + name + "\n"); + out.write("\n
\n\n"); + } + } + out.write("
\n"); + out.write("Custom links\n"); + out.write("\n"); + out.write("
"); + out.write("
"); + out.write("Secured by Syndie"); + out.write("
\n"); + out.write("
\n\n"); + } + + private String renderLink(PetName pn) { + return "" + HTMLRenderer.sanitizeString(pn.getName()) + ""; + } + + 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("
\n\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("
\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("
\n"); + renderComments(user, out, entry.getURI(), archive, index, renderer); + out.write("
\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("\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("

\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("" + + POSTS_PER_PAGE + " older entries"); + out.write(" | "); + if (offset <= 0) + out.write(POSTS_PER_PAGE + " more recent entries"); + else + out.write("" + POSTS_PER_PAGE + " more recent entries"); + + out.write("
\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 = +"