You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
539 lines
19 KiB
539 lines
19 KiB
// BlogBoard.java
|
|
// -------------------------------------
|
|
// (C) by Michael Peter Christen; mc@yacy.net
|
|
// first published on http://www.anomic.de
|
|
// Frankfurt, Germany, 2004
|
|
// last major change: 20.07.2004
|
|
//
|
|
// $LastChangedDate$
|
|
// $LastChangedRevision$
|
|
// $LastChangedBy$
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
// This file is contributed by Jan Sandbrink
|
|
// based on the Code of wikiBoard.java
|
|
|
|
package net.yacy.data;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.text.ParseException;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.TreeSet;
|
|
|
|
import javax.xml.parsers.DocumentBuilder;
|
|
import javax.xml.parsers.DocumentBuilderFactory;
|
|
import javax.xml.parsers.ParserConfigurationException;
|
|
|
|
import net.yacy.cora.date.GenericFormatter;
|
|
import net.yacy.cora.document.encoding.UTF8;
|
|
import net.yacy.cora.order.Base64Order;
|
|
import net.yacy.cora.order.NaturalOrder;
|
|
import net.yacy.cora.protocol.Domains;
|
|
import net.yacy.cora.util.ConcurrentLog;
|
|
import net.yacy.cora.util.SpaceExceededException;
|
|
import net.yacy.data.wiki.WikiBoard;
|
|
import net.yacy.kelondro.blob.MapHeap;
|
|
import net.yacy.kelondro.util.kelondroException;
|
|
|
|
import org.w3c.dom.Document;
|
|
import org.w3c.dom.Node;
|
|
import org.w3c.dom.NodeList;
|
|
import org.xml.sax.SAXException;
|
|
|
|
|
|
public class BlogBoard {
|
|
|
|
private static final int KEY_LENGTH = 64;
|
|
|
|
private MapHeap database = null;
|
|
|
|
public BlogBoard(final File actpath) throws IOException {
|
|
new File(actpath.getParent()).mkdir();
|
|
//database = new MapView(BLOBTree.toHeap(actpath, true, true, keyLength, recordSize, '_', NaturalOrder.naturalOrder, newFile), 500, '_');
|
|
this.database = new MapHeap(actpath, KEY_LENGTH, NaturalOrder.naturalOrder, 1024 * 64, 500, '_');
|
|
}
|
|
|
|
public int size() {
|
|
return this.database.size();
|
|
}
|
|
|
|
/**
|
|
* Tells if the database contains an element.
|
|
* @param key the ID of the element
|
|
* @return true if the database contains the element, else false
|
|
*/
|
|
public boolean contains(final String key) {
|
|
return this.database.containsKey(UTF8.getBytes(key));
|
|
}
|
|
|
|
public synchronized void close() {
|
|
this.database.close();
|
|
}
|
|
|
|
private static String normalize(final String key) {
|
|
return (key == null) ? "null" : key.trim().toLowerCase();
|
|
}
|
|
|
|
public static String webalize(final String key) {
|
|
return (key == null) ? "null": key.trim().toLowerCase().replaceAll(" ", "%20");
|
|
}
|
|
|
|
public String guessAuthor(final String ip) {
|
|
return WikiBoard.guessAuthor(ip);
|
|
}
|
|
|
|
/**
|
|
* Create a new BlogEntry and return it
|
|
* @param key
|
|
* @param subject
|
|
* @param author
|
|
* @param ip
|
|
* @param date
|
|
* @param page the content of the Blogentry
|
|
* @param comments
|
|
* @param commentMode possible params are: 0 - no comments allowed, 1 - comments allowed, 2 - comments moderated
|
|
* @return BlogEntry
|
|
*/
|
|
public BlogEntry newEntry(final String key, final byte[] subject, final byte[] author, final String ip, final Date date, final byte[] page, final List<String> comments, final String commentMode) {
|
|
return new BlogEntry(normalize(key), subject, author, ip, date, page, comments, commentMode);
|
|
}
|
|
|
|
/*
|
|
* writes a new page and return the key
|
|
*/
|
|
public String writeBlogEntry(final BlogEntry page) {
|
|
String ret = null;
|
|
try {
|
|
this.database.insert(UTF8.getBytes(page.key), page.record);
|
|
ret = page.key;
|
|
} catch (final IOException ex) {
|
|
ConcurrentLog.logException(ex);
|
|
} catch (final SpaceExceededException ex) {
|
|
ConcurrentLog.logException(ex);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public BlogEntry readBlogEntry(final String key) {
|
|
return readBlogEntry(key, this.database);
|
|
}
|
|
|
|
private BlogEntry readBlogEntry(final String key, final MapHeap base) {
|
|
final String normalized = normalize(key);
|
|
Map<String, String> record;
|
|
try {
|
|
record = base.get(UTF8.getBytes(normalized.substring(0, Math.min(normalized.length(), KEY_LENGTH))));
|
|
} catch (final IOException e) {
|
|
ConcurrentLog.logException(e);
|
|
record = null;
|
|
} catch (final SpaceExceededException e) {
|
|
ConcurrentLog.logException(e);
|
|
record = null;
|
|
}
|
|
return (record == null) ?
|
|
newEntry(key, new byte[0], UTF8.getBytes("anonymous"), Domains.LOCALHOST, new Date(), new byte[0], null, null) :
|
|
new BlogEntry(key, record);
|
|
}
|
|
|
|
public boolean importXML(final String input) {
|
|
if (input != null && !input.isEmpty()) {
|
|
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
try {
|
|
final DocumentBuilder builder = factory.newDocumentBuilder();
|
|
return parseXMLimport(builder.parse(new ByteArrayInputStream(UTF8.getBytes(input))));
|
|
} catch (final ParserConfigurationException ex) {
|
|
ConcurrentLog.logException(ex);
|
|
} catch (final SAXException ex) {
|
|
ConcurrentLog.logException(ex);
|
|
} catch (final IOException ex) {
|
|
ConcurrentLog.logException(ex);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean parseXMLimport(final Document doc) {
|
|
if(!"blog".equals(doc.getDocumentElement().getTagName())) {
|
|
return false;
|
|
}
|
|
|
|
final NodeList items = doc.getDocumentElement().getElementsByTagName("item");
|
|
if(items.getLength() == 0) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0, n = items.getLength(); i < n; ++i) {
|
|
String key = null, ip = null, StrSubject = null, StrAuthor = null, StrPage = null, StrDate = null;
|
|
Date date = null;
|
|
|
|
if(!"item".equals(items.item(i).getNodeName())) continue;
|
|
|
|
final NodeList currentNodeChildren = items.item(i).getChildNodes();
|
|
|
|
for (int j = 0, m = currentNodeChildren.getLength(); j < m; ++j) {
|
|
final Node currentNode = currentNodeChildren.item(j);
|
|
if ("id".equals(currentNode.getNodeName())) {
|
|
key = currentNode.getFirstChild().getNodeValue();
|
|
} else if ("ip".equals(currentNode.getNodeName())) {
|
|
ip = currentNode.getFirstChild().getNodeValue();
|
|
} else if ("timestamp".equals(currentNode.getNodeName())) {
|
|
StrDate = currentNode.getFirstChild().getNodeValue();
|
|
} else if ("subject".equals(currentNode.getNodeName())) {
|
|
StrSubject = currentNode.getFirstChild().getNodeValue();
|
|
} else if ("author".equals(currentNode.getNodeName())) {
|
|
StrAuthor = currentNode.getFirstChild().getNodeValue();
|
|
} else if ("content".equals(currentNode.getNodeName())) {
|
|
StrPage = currentNode.getFirstChild().getNodeValue();
|
|
}
|
|
}
|
|
|
|
try {
|
|
date = GenericFormatter.SHORT_SECOND_FORMATTER.parse(StrDate, 0).getTime();
|
|
} catch (final ParseException e1) {
|
|
date = new Date();
|
|
}
|
|
|
|
if(key == null || ip == null || StrSubject == null || StrAuthor == null || StrPage == null || date == null) {
|
|
return false;
|
|
}
|
|
|
|
byte[] subject,author,page;
|
|
subject = UTF8.getBytes(StrSubject);
|
|
author = UTF8.getBytes(StrAuthor);
|
|
page = UTF8.getBytes(StrPage);
|
|
writeBlogEntry (newEntry(key, subject, author, ip, date, page, null, null));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void deleteBlogEntry(final String key) {
|
|
try {
|
|
this.database.delete(UTF8.getBytes(normalize(key)));
|
|
} catch (final IOException e) { }
|
|
}
|
|
|
|
public Iterator<byte[]> keys(final boolean up) throws IOException {
|
|
return this.database.keys(up, false);
|
|
}
|
|
|
|
/**
|
|
* Comparator to sort objects of type Blog according to their timestamps
|
|
*/
|
|
public class BlogComparator implements Comparator<String> {
|
|
|
|
private final boolean newestFirst;
|
|
|
|
/**
|
|
* @param newestFirst newest first, or oldest first?
|
|
*/
|
|
public BlogComparator(final boolean newestFirst){
|
|
this.newestFirst = newestFirst;
|
|
}
|
|
|
|
@Override
|
|
public int compare(final String obj1, final String obj2) {
|
|
final BlogEntry blogEntry1 = readBlogEntry(obj1);
|
|
final BlogEntry blogEntry2 = readBlogEntry(obj2);
|
|
if (blogEntry1 == null || blogEntry2 == null)
|
|
return 0;
|
|
if (this.newestFirst) {
|
|
if (Long.parseLong(blogEntry2.getTimestamp()) - Long.parseLong(blogEntry1.getTimestamp()) > 0) {
|
|
return 1;
|
|
}
|
|
return -1;
|
|
}
|
|
if (Long.parseLong(blogEntry1.getTimestamp()) - Long.parseLong(blogEntry2.getTimestamp()) > 0) {
|
|
return 1;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
public Iterator<String> getBlogIterator(final boolean priv){
|
|
final Set<String> set = new TreeSet<String>(new BlogComparator(true));
|
|
final Iterator<BlogEntry> iterator = blogIterator(true);
|
|
BlogEntry blogEntry;
|
|
while (iterator.hasNext()) {
|
|
blogEntry = iterator.next();
|
|
if (priv || blogEntry.isPublic()) {
|
|
set.add(blogEntry.getKey());
|
|
}
|
|
}
|
|
return set.iterator();
|
|
}
|
|
|
|
public Iterator<BlogEntry> blogIterator(final boolean up){
|
|
try {
|
|
return new BlogIterator(up);
|
|
} catch (final IOException e) {
|
|
return new HashSet<BlogEntry>().iterator();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Subclass of blogBoard, which provides the blogIterator object-type
|
|
*/
|
|
public class BlogIterator implements Iterator<BlogEntry> {
|
|
Iterator<byte[]> blogIter;
|
|
//blogBoard.BlogEntry nextEntry;
|
|
public BlogIterator(final boolean up) throws IOException {
|
|
this.blogIter = BlogBoard.this.database.keys(up, false);
|
|
//this.nextEntry = null;
|
|
}
|
|
|
|
@Override
|
|
public boolean hasNext() {
|
|
try {
|
|
return this.blogIter.hasNext();
|
|
} catch (final kelondroException e) {
|
|
//resetDatabase();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public BlogEntry next() {
|
|
try {
|
|
return readBlogEntry(UTF8.String(this.blogIter.next()));
|
|
} catch (final kelondroException e) {
|
|
//resetDatabase();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void remove() {
|
|
// if (this.nextEntry != null) {
|
|
// try {
|
|
// final Object blogKey = this.nextEntry.getKey();
|
|
// if (blogKey != null) deleteBlogEntry((String) blogKey);
|
|
// } catch (final kelondroException e) {
|
|
// //resetDatabase();
|
|
// }
|
|
// }
|
|
throw new UnsupportedOperationException("Method not implemented yet.");
|
|
}
|
|
}
|
|
|
|
public class BlogEntry {
|
|
|
|
String key;
|
|
Map<String, String> record;
|
|
|
|
public BlogEntry(final String nkey, final byte[] subject, final byte[] author, final String ip, final Date date, final byte[] page, final List<String> comments, final String commentMode) {
|
|
this.record = new HashMap<String, String>();
|
|
setKey(nkey);
|
|
setDate(date);
|
|
setSubject(subject);
|
|
setAuthor(author);
|
|
setIp(ip);
|
|
setPage(page);
|
|
setComments(comments);
|
|
setCommentMode(commentMode);
|
|
|
|
// TODO: implement this function
|
|
this.record.put("privacy", "public");
|
|
|
|
WikiBoard.setAuthor(ip, UTF8.String(author));
|
|
}
|
|
|
|
BlogEntry(final String key, final Map<String, String> record) {
|
|
this.key = key;
|
|
this.record = record;
|
|
if (this.record.get("comments") == null) {
|
|
this.record.put("comments", ListManager.collection2string(new ArrayList<String>()));
|
|
}
|
|
if (this.record.get("commentMode") == null || this.record.get("commentMode").length() < 1) {
|
|
this.record.put("commentMode", "2");
|
|
}
|
|
}
|
|
|
|
private void setKey(final String key) {
|
|
this.key = key.substring(0, Math.min(key.length(), KEY_LENGTH));
|
|
}
|
|
|
|
public String getKey() {
|
|
return this.key;
|
|
}
|
|
|
|
public byte[] getSubject() {
|
|
final String m = this.record.get("subject");
|
|
if (m == null) {
|
|
return new byte[0];
|
|
}
|
|
final byte[] b = Base64Order.enhancedCoder.decode(m);
|
|
return (b == null) ? new byte[0] : b;
|
|
}
|
|
|
|
private void setSubject(final byte[] subject) {
|
|
if (subject == null) {
|
|
this.record.put("subject","");
|
|
} else {
|
|
this.record.put("subject", Base64Order.enhancedCoder.encode(subject));
|
|
}
|
|
}
|
|
|
|
public Date getDate() {
|
|
try {
|
|
final String date = this.record.get("date");
|
|
if (date == null) {
|
|
if (ConcurrentLog.isFinest("Blog")) {
|
|
ConcurrentLog.finest("Blog", "ERROR: date field missing in blogBoard");
|
|
}
|
|
return new Date();
|
|
}
|
|
return GenericFormatter.SHORT_SECOND_FORMATTER.parse(date, 0).getTime();
|
|
} catch (final ParseException ex) {
|
|
return new Date();
|
|
}
|
|
}
|
|
|
|
private void setDate(final Date date) {
|
|
Date ret = date;
|
|
if (ret == null) {
|
|
ret = new Date();
|
|
}
|
|
this.record.put("date", GenericFormatter.SHORT_SECOND_FORMATTER.format(ret));
|
|
}
|
|
|
|
public String getTimestamp() {
|
|
final String timestamp = this.record.get("date");
|
|
if (timestamp == null) {
|
|
if (ConcurrentLog.isFinest("Blog")) {
|
|
ConcurrentLog.finest("Blog", "ERROR: date field missing in blogBoard");
|
|
}
|
|
return GenericFormatter.SHORT_SECOND_FORMATTER.format();
|
|
}
|
|
return timestamp;
|
|
}
|
|
|
|
public byte[] getAuthor() {
|
|
final String author = this.record.get("author");
|
|
if (author == null) {
|
|
return new byte[0];
|
|
}
|
|
final byte[] b = Base64Order.enhancedCoder.decode(author);
|
|
return (b == null) ? new byte[0] : b;
|
|
}
|
|
|
|
private void setAuthor(final byte[] author) {
|
|
if (author == null)
|
|
this.record.put("author","");
|
|
else
|
|
this.record.put("author", Base64Order.enhancedCoder.encode(author));
|
|
}
|
|
|
|
public int getCommentsSize() {
|
|
// This ist a Bugfix for Version older than 4443.
|
|
if (this.record.get("comments").startsWith(",")) {
|
|
this.record.put("comments", this.record.get("comments").substring(1));
|
|
writeBlogEntry(this);
|
|
}
|
|
final List<String> commentsize = ListManager.string2arraylist(this.record.get("comments"));
|
|
return commentsize.size();
|
|
}
|
|
|
|
public List<String> getComments() {
|
|
return ListManager.string2arraylist(this.record.get("comments"));
|
|
}
|
|
|
|
private void setComments(final List<String> comments) {
|
|
if (comments == null) {
|
|
this.record.put("comments", ListManager.collection2string(new ArrayList<String>()));
|
|
} else {
|
|
this.record.put("comments", ListManager.collection2string(comments));
|
|
}
|
|
}
|
|
|
|
public String getIp() {
|
|
final String ip = this.record.get("ip");
|
|
return (ip == null) ? Domains.LOCALHOST : ip;
|
|
}
|
|
|
|
private void setIp(final String ip) {
|
|
String ret = ip;
|
|
if ((ret == null) || (ret.isEmpty()))
|
|
ret = "";
|
|
this.record.put("ip", ret);
|
|
}
|
|
|
|
public byte[] getPage() {
|
|
final String page = this.record.get("page");
|
|
if (page == null) {
|
|
return new byte[0];
|
|
}
|
|
final byte[] page_as_byte = Base64Order.enhancedCoder.decode(page);
|
|
return (page_as_byte == null) ? new byte[0] : page_as_byte;
|
|
}
|
|
|
|
private void setPage(final byte[] page) {
|
|
if (page == null) {
|
|
this.record.put("page", "");
|
|
} else {
|
|
this.record.put("page", Base64Order.enhancedCoder.encode(page));
|
|
}
|
|
}
|
|
|
|
public void addComment(final String commentID) {
|
|
final List<String> comments = ListManager.string2arraylist(this.record.get("comments"));
|
|
comments.add(commentID);
|
|
this.record.put("comments", ListManager.collection2string(comments));
|
|
}
|
|
|
|
public boolean removeComment(final String commentID) {
|
|
final List<String> comments = ListManager.string2arraylist(this.record.get("comments"));
|
|
final boolean success = comments.remove(commentID);
|
|
this.record.put("comments", ListManager.collection2string(comments));
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* returns the comment mode
|
|
* 0 - no comments allowed
|
|
* 1 - comments allowed
|
|
* 2 - comments moderated
|
|
* @return comment mode
|
|
*/
|
|
public int getCommentMode(){
|
|
return Integer.parseInt(this.record.get("commentMode"));
|
|
}
|
|
|
|
private void setCommentMode(final String mode) {
|
|
if (mode == null) {
|
|
this.record.put("commentMode", "2");
|
|
} else {
|
|
this.record.put("commentMode", mode);
|
|
}
|
|
}
|
|
|
|
public boolean isPublic() {
|
|
final String privacy = this.record.get("privacy");
|
|
return (privacy == null || privacy.equalsIgnoreCase("public")) ? true : false;
|
|
}
|
|
|
|
}
|
|
}
|