package thaw.plugins.miniFrost.frostKSK;
import java.util.Vector;
import java.util.Observer;
import java.util.Observable;
import java.io.File;
import java.text.SimpleDateFormat;
import java.sql.*;
import thaw.fcp.*;
import thaw.core.Logger;
import thaw.core.I18n;
import thaw.plugins.Hsqldb;
import thaw.plugins.signatures.Identity;
import thaw.plugins.miniFrost.interfaces.Message;
/**
* only notify when the message has been fully parsed
*/
public class KSKMessage
extends Observable
implements Message, Observer {
public final static int FCP_PRIORITY = 2; /* below 2, the node doesn't react ?! */
public final static int FCP_MAX_RETRIES = 1; /* we don't have time to loose, in the worst case,
* we will come back later
*/
public final static int FCP_MAX_SIZE = 32*1024;
/* content is not kept in memory (at least not here) */
private int id;
private String idStr;
private String inReplyToStr;
private String subject;
private KSKAuthor author;
private java.util.Date date;
private int rev;
private boolean read;
private boolean archived;
private Identity encryptedFor;
private KSKBoard board;
public KSKMessage(KSKBoard board,
java.util.Date date, int rev) {
this.board = board;
this.date = date;
this.rev = rev;
}
private Hsqldb db;
private boolean downloading = false; /* put back to false only after parsing */
private boolean successfullyDownloaded = false;
private boolean successfullyParsed = false;
private FCPQueueManager queueManager = null;
private String key = null;
public void download(FCPQueueManager queueManager, Hsqldb db) {
this.db = db;
this.queueManager = queueManager;
downloading = true;
key = board.getDownloadKey(date, rev);
Logger.info(this, "Fetching : "+key.toString());
FCPClientGet get = new FCPClientGet(key, FCP_PRIORITY,
FCPClientGet.PERSISTENCE_UNTIL_DISCONNECT,
false /* globalQueue */,
FCP_MAX_RETRIES,
System.getProperty("java.io.tmpdir"),
FCP_MAX_SIZE,
true /* noDDA */);
get.setNoRedirectionFlag(true);
get.addObserver(this);
/* we override the queueManager */
get.start(queueManager);
}
public void update(Observable o, Object param) {
FCPClientGet get = (FCPClientGet)o;
if (!get.isFinished())
return;
if (!get.isSuccessful()) {
int code = get.getGetFailedCode();
if (code == 21 /* Too big */
|| code == 28 /* All data not found */) {
Logger.warning(this, "MiniFrost: Invalid key: "+key);
successfullyDownloaded = true;
board.addInvalidSlot(date, rev);
} else if (get.getProtocolErrorCode() == 4 /* URI parse error */
|| get.getProtocolErrorCode() == 20 /* URL parse error */
|| code == 20 /* Invalid URI */) {
Logger.warning(this, "MiniFrost: Invalid key: "+key);
successfullyDownloaded = true;
} else if (get.getProtocolErrorCode() >= 0) {
Logger.warning(this,
"MiniFrost: Unknown protocol error (code="+
Integer.toString(get.getProtocolErrorCode())+"). Please report.");
successfullyDownloaded = true;
} else if (code == 13 /* dnd */
|| code == 14 /* route not found */
|| code == 20 /* jflesch is stupid */) {
Logger.info(this, key+" not found");
successfullyDownloaded = false;
} else {
Logger.notice(this, "Problem with "+key + " ; code : "+Integer.toString(code));
successfullyDownloaded = true;
}
downloading = false;
successfullyParsed = false;
} else {
Logger.info(this, key+" found => parsing");
KSKMessageParser parser = new KSKMessageParser();
if (!parser.loadFile(new File(get.getPath()), db)) {
/* invalid slot */
Logger.notice(this, " message: '"+board.getName()+"'"
+" - "+date.toString()
+" - "+Integer.toString(rev));
board.addInvalidSlot(date, rev);
new File(get.getPath()).delete();
successfullyDownloaded = true;
downloading = false;
successfullyParsed = false;
read = false;
} else if (parser.checkSignature(db)
&& parser.filter(board.getFactory().getPlugin().getRegexpBlacklist())
&& parser.insert(db, board.getId(),
date, rev, board.getName())) {
if (parser.getTrustListPublicKey() != null) {
board.getFactory().getWoT().addTrustList(parser.getIdentity(),
parser.getTrustListPublicKey(),
parser.getDate());
}
new File(get.getPath()).delete();
Logger.info(this, "Parsing ok");
successfullyDownloaded = true;
downloading = false;
successfullyParsed = true;
read = parser.mustBeDisplayedAsRead();
} else {
new File(get.getPath()).delete();
Logger.notice(this, "Unable to parse.");
successfullyDownloaded = true;
downloading = false;
successfullyParsed = false;
read = true;
}
}
get.stop(queueManager);
queueManager.remove(get);
setChanged();
notifyObservers();
}
public boolean isDownloading() {
return downloading;
}
/**
* @return true if the FCPClientGet didn't end on a 'Data not found',
* a 'Route not found', etc
*/
public boolean isSuccessful() {
return successfullyDownloaded;
}
/**
* @return true if the message was correctly parsed
* Note: it returns false if the signature was invalid.
*/
public boolean isParsable() {
return successfullyParsed;
}
public String getMsgId() {
return idStr;
}
public String getInReplyToId() {
return inReplyToStr;
}
public KSKMessage(int id, String idStr,
String inReplyToStr,
String subject, String nick,
int sigId, Identity identity,
java.util.Date date, int rev,
boolean read, boolean archived,
Identity encryptedFor,
KSKBoard board) {
this.id = id;
this.idStr = idStr;
this.inReplyToStr = inReplyToStr;
this.subject = subject;
this.author = new KSKAuthor(nick, identity);
this.date = date;
this.rev = rev;
this.read = read;
this.archived = archived;
this.board = board;
this.encryptedFor = encryptedFor;
}
public String getSubject() {
return subject;
}
public thaw.plugins.miniFrost.interfaces.Author getSender() {
return author;
}
public java.util.Date getDate() {
return date;
}
public int compareTo(Object o) {
if (getDate() == null && ((Message)o).getDate() != null)
return -1;
if (getDate() != null && ((Message)o).getDate() == null)
return 1;
if (getDate() == null && ((Message)o).getDate() == null)
return 0;
int c = getDate().compareTo( ((Message)o).getDate());
return -1 * c;
}
public int getRev() {
return rev;
}
public boolean isArchived() {
return archived;
}
public boolean isRead() {
return read;
}
public Identity encryptedFor() {
return encryptedFor;
}
public thaw.plugins.miniFrost.interfaces.Board getBoard() {
return board;
}
public void setRead(boolean read) {
if (read == this.read)
return;
this.read = read;
try {
Hsqldb db = board.getFactory().getDb();
synchronized(db.dbLock) {
PreparedStatement st;
st = db.getConnection().prepareStatement("UPDATE frostKSKMessages "+
"SET read = ? "+
"WHERE id = ?");
st.setBoolean(1, read);
st.setInt(2, id);
st.execute();
st.close();
}
} catch(SQLException e) {
Logger.error(this, "Can't update read status because : "+e.toString());
}
}
public void setArchived(boolean archived) {
if (archived == this.archived)
return;
this.archived = archived;
try {
Hsqldb db = board.getFactory().getDb();
synchronized(db.dbLock) {
PreparedStatement st;
st = db.getConnection().prepareStatement("UPDATE frostKSKMessages "+
"SET archived = ? "+
"WHERE id = ?");
st.setBoolean(1, archived);
st.setInt(2, id);
st.execute();
st.close();
}
} catch(SQLException e) {
Logger.error(this, "Can't update archived status because : "+e.toString());
}
}
protected Vector parseMessage(final String fullMsg) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd - HH:mm:ss");
//sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
Vector v = new Vector();
String[] split = fullMsg.split("(\\A|[^-])-----[^-]");
if (split.length < 4) {
Logger.notice(this, "Not enought elements in the messages");
return null;
}
/* the first element of split is expected to be empty */
if (split[0] == null || !"".equals(split[0])) {
Logger.notice(this, "Does not begin as expected ?");
return null;
}
for (int i = 0 ; i < split.length ; i++) {
split[i] = split[i].trim();
}
for (int i = 1 ; i < split.length ; i += 3) {
/* expect 3 elements */
if ((i + 2) >= split.length) {
Logger.notice(this, "Not enought elements in the last part");
return null;
}
String author = split[i];
String dateStr = split[i+1].replaceAll("GMT", "");
String msg = split[i+2];
java.util.Date date = sdf.parse(dateStr, new java.text.ParsePosition(0));
if (date == null) {
Logger.notice(this, "Unable to parse the date : "+dateStr);
return null;
}
v.add(new KSKSubMessage(new KSKAuthor(author, null),
date, msg));
}
return v;
}
public String getRawMessage() {
try {
Hsqldb db = board.getFactory().getDb();
synchronized(db.dbLock) {
PreparedStatement st;
st = db.getConnection().prepareStatement("SELECT content "+
"FROM frostKSKMessages "+
"WHERE id = ? "+
"LIMIT 1");
st.setInt(1, id);
ResultSet set = st.executeQuery();
if (!set.next()) {
st.close();
return null;
}
String s = set.getString("content");
st.close();
return s;
}
} catch(SQLException e) {
Logger.error(this, "Error while getting the messages : "+e.toString());
return null;
}
}
private void setAuthorAndDate(Vector subMsgs, KSKAuthor author,
java.util.Date date) {
// we browse the vector by starting with the last element
// so we can't use an iterator
// the goal is to find the corresponding KSKAuthor
// and replace it by ours (ours has its identity set)
for (int i = subMsgs.size()-1; i >= 0 ; i--) {
KSKSubMessage sub = (KSKSubMessage)subMsgs.get(i);
if (author.toString().equals(sub.getAuthor().toString())) {
sub.setAuthor(author);
sub.setDate(date);
return;
}
}
// we didn't find it, so we force it on the last message
Logger.notice(this, "KSKAuthor forced on the last sub-messages");
KSKSubMessage lastMsg = (KSKSubMessage)subMsgs.get(subMsgs.size()-1);
lastMsg.setAuthor(author);
lastMsg.setDate(date);
}
/** no caching */
public Vector getSubMessages() {
String content;
try {
if (board == null)
Logger.error(this, "No ref to the corresponding board ?!");
else if (board.getFactory() == null)
Logger.error(this, "Can't access the board factory ?!");
Hsqldb db = board.getFactory().getDb();
synchronized(db.dbLock) {
PreparedStatement st;
st = db.getConnection().prepareStatement("SELECT content "+
"FROM frostKSKMessages "+
"WHERE id = ? "+
"LIMIT 1");
st.setInt(1, id);
ResultSet set = st.executeQuery();
if (!set.next()) {
st.close();
return null;
}
content = set.getString("content");
st.close();
}
} catch(SQLException e) {
Logger.error(this, "Error while getting the messages : "+e.toString());
return null;
}
boolean parsed = true;
Vector v = null;
try {
v = parseMessage(content);
if (v == null || (v.size()) <= 0) {
parsed = false;
} else {
setAuthorAndDate(v, ((KSKAuthor)getSender()), getDate());
}
} catch(Exception e) { /* dirty, but should work */
Logger.error(this, "Error while parsing : "+e.toString());
parsed = false;
}
if (!parsed) {
Logger.warning(this, "Unable to parse the message ?! returning raw content");
v = new Vector();
v.add(new KSKSubMessage((KSKAuthor)getSender(),
getDate(),
"==== "+I18n.getMessage("thaw.plugin.miniFrost.rawMessage")+" ===="
+"\n\n"+content));
}
return v;
}
protected int getId() {
return id;
}
public Vector getAttachments() {
return KSKAttachmentFactory.getAttachments(this,
board.getFactory(),
board.getFactory().getDb());
}
public boolean equals(Object o) {
if (!(o instanceof KSKMessage))
return false;
return (((KSKMessage)o).getId() == id);
}
public static boolean destroyAll(KSKBoard board, Hsqldb db) {
if (!KSKFileAttachment.destroyAll(board, db)
|| !KSKBoardAttachment.destroyAll(board, db))
return false;
try {
synchronized(db.dbLock) {
PreparedStatement st;
/* to avoid the problems with the constraints */
st = db.getConnection().prepareStatement("UPDATE frostKSKMessages SET "+
"inReplyTo = NULL");
st.execute();
st.close();
st = db.getConnection().prepareStatement("DELETE FROM frostKSKMessages "+
"WHERE boardId = ?");
st.setInt(1, board.getId());
st.execute();
st.close();
}
} catch(SQLException e) {
Logger.error(null, "Can't destroy the board messages because : "+e.toString());
return false;
}
return true;
}
public boolean destroy(Hsqldb db) {
if (!KSKFileAttachment.destroy(this, db)
|| !KSKBoardAttachment.destroy(this, db))
return false;
try {
synchronized(db.dbLock) {
PreparedStatement st;
/* to avoid the integrity constraint violations */
st = db.getConnection().prepareStatement("UPDATE frostKSKMessages SET "+
"inReplyTo = NULL WHERE inReplyTo = ?");
st.setInt(1, id);
st.execute();
st.close();
st = db.getConnection().prepareStatement("DELETE FROM frostKSKMessages "+
"WHERE id = ?");
st.setInt(1, id);
st.execute();
st.close();
}
} catch(SQLException e) {
Logger.error(null, "Can't destroy the message "+Integer.toString(id)+" because : "+e.toString());
return false;
}
return true;
}
}