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.
445 lines
15 KiB
445 lines
15 KiB
//wikiBoard.java
|
|
//-------------------------------------
|
|
//(C) by Michael Peter Christen; mc@yacy.net
|
|
//first published on http://www.anomic.de
|
|
//Frankfurt, Germany, 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
|
|
|
|
package de.anomic.data.wiki;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.text.ParseException;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.TimeZone;
|
|
|
|
import net.yacy.cora.document.ASCII;
|
|
import net.yacy.cora.document.UTF8;
|
|
import net.yacy.cora.protocol.Domains;
|
|
import net.yacy.kelondro.blob.MapHeap;
|
|
import net.yacy.kelondro.index.RowSpaceExceededException;
|
|
import net.yacy.kelondro.logging.Log;
|
|
import net.yacy.kelondro.order.Base64Order;
|
|
import net.yacy.kelondro.order.NaturalOrder;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public class WikiBoard {
|
|
|
|
public static final int keyLength = 64;
|
|
private static final String DATE_FORMAT = "yyyyMMddHHmmss";
|
|
private static final String ANONYMOUS = "anonymous";
|
|
|
|
protected static final SimpleDateFormat SimpleFormatter = new SimpleDateFormat(DATE_FORMAT, Locale.US);
|
|
|
|
static {
|
|
SimpleFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
|
|
}
|
|
|
|
private MapHeap datbase = null;
|
|
private MapHeap bkpbase = null;
|
|
private static final Map<String, String> AUTHORS = new HashMap<String, String>();
|
|
|
|
/**
|
|
* Constructor.
|
|
* @param actpath path of database which contains current wiki data.
|
|
* @param bkppath path of backup database.
|
|
* @throws IOException if error occurs during HDD access.
|
|
*/
|
|
public WikiBoard(final File actpath, final File bkppath) throws IOException {
|
|
new File(actpath.getParent()).mkdirs();
|
|
if (this.datbase == null) {
|
|
//datbase = new MapView(BLOBTree.toHeap(actpath, true, true, keyLength, recordSize, '_', NaturalOrder.naturalOrder, actpathNew), 500, '_');
|
|
this.datbase = new MapHeap(actpath, keyLength, NaturalOrder.naturalOrder, 1024 * 64, 500, '_');
|
|
}
|
|
new File(bkppath.getParent()).mkdirs();
|
|
if (this.bkpbase == null) {
|
|
//bkpbase = new MapView(BLOBTree.toHeap(bkppath, true, true, keyLength + dateFormat.length(), recordSize, '_', NaturalOrder.naturalOrder, bkppathNew), 500, '_');
|
|
this.bkpbase = new MapHeap(bkppath, keyLength + DATE_FORMAT.length(), NaturalOrder.naturalOrder, 1024 * 64, 500, '_');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets total number of entries of wiki DB and DB which contains backup entries.
|
|
* @return number of entries in wiki plus number of old entries.
|
|
*/
|
|
public int sizeOfTwo() {
|
|
return this.datbase.size() + this.bkpbase.size();
|
|
}
|
|
|
|
/**
|
|
* Gets number of entries of wiki DB.
|
|
* @return number of entries in wiki.
|
|
*/
|
|
public int size() {
|
|
return this.datbase.size();
|
|
}
|
|
|
|
/**
|
|
* Closes database files.
|
|
*/
|
|
public synchronized void close() {
|
|
this.datbase.close();
|
|
this.bkpbase.close();
|
|
}
|
|
|
|
/**
|
|
* Gets current date.
|
|
* @return current date.
|
|
*/
|
|
static String dateString() {
|
|
return dateString(new Date());
|
|
}
|
|
|
|
/**
|
|
* Gets String representation of a Date.
|
|
* @param date the Date.
|
|
* @return String representation of Date.
|
|
*/
|
|
public static String dateString(final Date date) {
|
|
synchronized (SimpleFormatter) {
|
|
return SimpleFormatter.format(date);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets normalized version of a key.
|
|
* @param key the key.
|
|
* @return normalized version of key.
|
|
*/
|
|
private static String normalize(final String key) {
|
|
return (key != null) ? key.trim().toLowerCase() : "null";
|
|
}
|
|
|
|
/**
|
|
* Normalizes key and replaces spaces by escape code.
|
|
* @param key the key.
|
|
* @return normalized and webalized version.
|
|
*/
|
|
public static String webalize(final String key) {
|
|
return (key != null) ? normalize(key).replaceAll(" ", "%20") : "null";
|
|
}
|
|
|
|
/**
|
|
* Tries to guess the name of the author by a given IP address.
|
|
* @param ip the IP address.
|
|
* @return
|
|
*/
|
|
public static String guessAuthor(final String ip) {
|
|
final String author = AUTHORS.get(ip);
|
|
//yacyCore.log.logDebug("DEBUG: guessing author for ip = " + ip + " is '" + author + "', authors = " + authors.toString());
|
|
return author;
|
|
}
|
|
|
|
/**
|
|
* Adds an author name and a corresponding IP to internal Map.
|
|
* @param ip IP address of the author.
|
|
* @param author name of author.
|
|
*/
|
|
public static void setAuthor(final String ip, final String author) {
|
|
AUTHORS.put(ip,author);
|
|
}
|
|
|
|
/**
|
|
* Creates new Entry.
|
|
* @param subject subject of entry.
|
|
* @param author author of entry.
|
|
* @param ip IP address of author.
|
|
* @param reason reason for new Entry (for example "edit").
|
|
* @param page content of Entry.
|
|
* @return new Entry.
|
|
* @throws IOException
|
|
*/
|
|
public Entry newEntry(final String subject, final String author, final String ip, final String reason, final byte[] page) throws IOException {
|
|
return new Entry(normalize(subject), author, ip, reason, page);
|
|
}
|
|
|
|
/**
|
|
* Contains information of wiki page.
|
|
*/
|
|
public class Entry {
|
|
private static final String ANONYMOUS = "anonymous";
|
|
|
|
private final String key;
|
|
private final Map<String, String> record;
|
|
|
|
/**
|
|
* Constructor which creates new Entry using given information.
|
|
* @param subject subject of Entry.
|
|
* @param author author of Entry.
|
|
* @param ip IP address of author.
|
|
* @param reason reason for new Entry (for example "edit").
|
|
* @param page content of Entry.
|
|
* @throws IOException
|
|
*/
|
|
public Entry(final String subject, final String author, final String ip, final String reason, final byte[] page) throws IOException {
|
|
this.record = new HashMap<String, String>();
|
|
this.key = subject.substring(0, Math.min((subject != null) ? subject.length() : 0, keyLength));
|
|
this.record.put("date", dateString());
|
|
this.record.put("author", Base64Order.enhancedCoder.encodeString((author != null && author.length() > 0) ? author : ANONYMOUS));
|
|
this.record.put("ip", (ip != null && ip.length() > 0) ? ip : "");
|
|
this.record.put("reason", Base64Order.enhancedCoder.encodeString((reason != null && reason.length() > 0) ? reason : ""));
|
|
this.record.put("page", (page != null) ? Base64Order.enhancedCoder.encode(page) : "");
|
|
AUTHORS.put(ip, author);
|
|
}
|
|
|
|
/**
|
|
* Constructor which creates Entry using key and record.
|
|
* @param key key of Entry.
|
|
* @param record record which contains data.
|
|
*/
|
|
Entry(final String key, final Map<String, String> record) {
|
|
this.key = key;
|
|
this.record = record;
|
|
}
|
|
|
|
/**
|
|
* Gets subject of Entry.
|
|
* @return subject of entry.
|
|
*/
|
|
public String subject() {
|
|
return this.key;
|
|
}
|
|
|
|
/**
|
|
* Gets date of Entry.
|
|
* @return date of Entry.
|
|
*/
|
|
public Date date() {
|
|
Date ret;
|
|
try {
|
|
final String c = this.record.get("date");
|
|
if (c == null) {
|
|
System.out.println("DEBUG - ERROR: date field missing in wikiBoard");
|
|
ret = new Date();
|
|
} else {
|
|
synchronized (SimpleFormatter) {
|
|
ret = SimpleFormatter.parse(c);
|
|
}
|
|
}
|
|
} catch (final ParseException e) {
|
|
ret = new Date();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Gets author of Entry.
|
|
* @return author of Entry.
|
|
*/
|
|
public String author() {
|
|
final String a = this.record.get("author");
|
|
final byte[] b;
|
|
return (a != null && (b = Base64Order.enhancedCoder.decode(a)) != null) ? UTF8.String(b) : ANONYMOUS;
|
|
}
|
|
|
|
/**
|
|
* Gets reason for Entry.
|
|
* @return reason for Entry.
|
|
*/
|
|
public String reason() {
|
|
final String ret;
|
|
final String r = this.record.get("reason");
|
|
if (r != null) {
|
|
final byte[] b;
|
|
ret = ((b = Base64Order.enhancedCoder.decode(r)) != null) ? UTF8.String(b) : "unknown";
|
|
} else {
|
|
ret = "";
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Gets actual content of Entry.
|
|
* @return content of Entry.
|
|
*/
|
|
public byte[] page() {
|
|
final String m = this.record.get("page");
|
|
final byte[] b;
|
|
return (m != null && (b = Base64Order.enhancedCoder.decode(m)) != null) ? b : new byte[0];
|
|
}
|
|
|
|
/**
|
|
* Sets date of previous version of Entry.
|
|
* @param date date of previous version of Entry.
|
|
*/
|
|
void setAncestorDate(final Date date) {
|
|
this.record.put("bkp", dateString(date));
|
|
}
|
|
|
|
/**
|
|
* Gets date of previous version of Entry.
|
|
* @return date of previous version of Entry.
|
|
*/
|
|
private Date getAncestorDate() {
|
|
Date ret = null;
|
|
try {
|
|
final String c = this.record.get("date");
|
|
if (c != null) {
|
|
synchronized (SimpleFormatter) {
|
|
ret = SimpleFormatter.parse(c);
|
|
}
|
|
}
|
|
} catch (final ParseException e) {
|
|
ret = null;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Gets previous version of Entry.
|
|
* @return previous version of Entry.
|
|
*/
|
|
public Entry getAncestor() {
|
|
final Date ancDate = getAncestorDate();
|
|
return (ancDate == null) ? null : read(this.key + dateString(ancDate), WikiBoard.this.bkpbase);
|
|
}
|
|
|
|
/**
|
|
* Adds child of current Entry.
|
|
* @param subject subject of child of current Entry.
|
|
*/
|
|
void setChild(final String subject) {
|
|
this.record.put("child", Base64Order.enhancedCoder.encode(UTF8.getBytes(subject)));
|
|
}
|
|
|
|
/**
|
|
* Gets name (= subject) of child of this Entry.
|
|
* @return name of child of this Entry.
|
|
*/
|
|
private String getChildName() {
|
|
final String c = this.record.get("child");
|
|
final byte[] subject;
|
|
return (c != null && (subject = Base64Order.enhancedCoder.decode(c)) != null) ? ASCII.String(subject) : null;
|
|
}
|
|
|
|
/**
|
|
* Tells if Entry has child.
|
|
* @return true if has child, else false.
|
|
*/
|
|
public boolean hasChild() {
|
|
final String c = this.record.get("child");
|
|
return (c != null && Base64Order.enhancedCoder.decode(c) != null) ? true : false;
|
|
}
|
|
|
|
/**
|
|
* Gets child of this Entry.
|
|
* @return child of this Entry.
|
|
*/
|
|
public Entry getChild() {
|
|
final String childName = getChildName();
|
|
return (childName == null) ? null : read(childName, WikiBoard.this.datbase);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes content of Entry to database and returns key.
|
|
* @param entry Entry to be written.
|
|
* @return key of Entry.
|
|
*/
|
|
public String write(final Entry entry) {
|
|
// writes a new page and returns key
|
|
String key = null;
|
|
try {
|
|
// first load the old page
|
|
final Entry oldEntry = read(entry.key);
|
|
// set the bkp date of the new page to the date of the old page
|
|
final Date oldDate = oldEntry.date();
|
|
entry.setAncestorDate(oldDate);
|
|
oldEntry.setChild(entry.subject());
|
|
// write the backup
|
|
this.bkpbase.insert(UTF8.getBytes(entry.key + dateString(oldDate)), oldEntry.record);
|
|
// write the new page
|
|
this.datbase.insert(UTF8.getBytes(entry.key), entry.record);
|
|
key = entry.key;
|
|
} catch (final Exception e) {
|
|
Log.logException(e);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
* Reads content of Entry from database.
|
|
* @param key key of Entry.
|
|
* @return Entry which contains data.
|
|
*/
|
|
public Entry read(final String key) {
|
|
return read(key, this.datbase);
|
|
}
|
|
|
|
/**
|
|
* Reads content of Entry from database.
|
|
* @param key key of Entry.
|
|
* @param base database containing data.
|
|
* @return Entry which contains data.
|
|
*/
|
|
Entry read(final String key, final MapHeap base) {
|
|
Entry ret = null;
|
|
try {
|
|
String copyOfKey = normalize(key);
|
|
if (copyOfKey.length() > keyLength) {
|
|
copyOfKey = copyOfKey.substring(0, keyLength);
|
|
}
|
|
final Map<String, String> record = base.get(UTF8.getBytes(copyOfKey));
|
|
ret = (record == null) ? newEntry(copyOfKey, ANONYMOUS, Domains.LOCALHOST, "New Page", UTF8.getBytes("")) : new Entry(copyOfKey, record);
|
|
} catch (final IOException e) {
|
|
Log.logException(e);
|
|
} catch (RowSpaceExceededException e) {
|
|
Log.logException(e);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Reads old Entry from backup database.
|
|
* @param key key of Entry.
|
|
* @return the Entry.
|
|
*/
|
|
public Entry readBkp(final String key) {
|
|
return read(key, this.bkpbase);
|
|
}
|
|
|
|
/**
|
|
* Gets Iterator of keys in database.
|
|
* @param up
|
|
* @return keys of Entries in database.
|
|
* @throws IOException
|
|
*/
|
|
public Iterator<byte[]> keys(final boolean up) throws IOException {
|
|
return this.datbase.keys(up, false);
|
|
}
|
|
|
|
/**
|
|
* Gets Iterator of keys in backup database.
|
|
* @param up
|
|
* @return keys of Entries in backup database.
|
|
* @throws IOException
|
|
*/
|
|
public Iterator<byte[]> keysBkp(final boolean up) throws IOException {
|
|
return this.bkpbase.keys(up, false);
|
|
}
|
|
}
|