package thaw.plugins.index; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.Enumeration; import java.util.Observable; import java.util.Observer; import java.util.Vector; import java.util.Calendar; import javax.swing.JOptionPane; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import org.w3c.dom.Document; import org.w3c.dom.Element; import thaw.core.Main; import thaw.core.I18n; import thaw.core.Logger; import thaw.core.Config; import thaw.core.MainWindow; import thaw.fcp.FreenetURIHelper; import thaw.fcp.FCPClientGet; import thaw.fcp.FCPClientPut; import thaw.fcp.FCPQueueManager; import thaw.fcp.FCPTransferQuery; import thaw.fcp.FCPGenerateSSK; import thaw.plugins.Hsqldb; import thaw.plugins.signatures.Identity; import thaw.plugins.index.File; public class Index extends Observable implements MutableTreeNode, IndexTreeNode, Observer, IndexContainer { private final static long MAX_SIZE = 5242880; /* 5MB */ private final Hsqldb db; private int id; private TreeNode parentNode; private String publicKey = null; /* needed for display: */ private String privateKey = null; private int rev = -1; private String displayName = null; private boolean hasChanged = false; private boolean newComment = false; private boolean publishPrivateKey = false; private java.sql.Date date = null; /* loaded only if asked explictly */ private String realName = null; /* when all the comment fetching will failed, loading will stop */ public final static int COMMENT_FETCHING_RUNNING_AT_THE_SAME_TIME = 5; private int lastCommentRev = 0; private int nmbFailedCommentFetching = 0; private Config config; private boolean isNew; private boolean successful = true; private Index() { db = null; } public Index(Hsqldb db, Config config, int id) { this.db = db; this.config = config; this.id = id; } /** * Use it when you can have these infos easily ; else let the index do the job */ public Index(Hsqldb db, Config config, int id, TreeNode parentNode, String publicKey, int rev, String privateKey, boolean publishPrivateKey, String displayName, java.sql.Date insertionDate, boolean hasChanged, boolean newComment) { this(db, config, id); this.parentNode = parentNode; this.privateKey = privateKey; this.publishPrivateKey = publishPrivateKey; this.publicKey = publicKey; this.rev = rev; this.displayName = displayName; this.date = insertionDate; this.hasChanged = hasChanged; this.newComment = newComment; } public void setIsNewFlag() { isNew = true; } /** * Won't apply in the database ! */ public void setId(int id) { this.id = id; } /** * Is this node coming from the tree ? */ public boolean isInTree() { return (getParent() != null); } public TreeNode getParent() { return parentNode; } public Enumeration children() { return null; } public boolean getAllowsChildren() { return false; } public TreeNode getChildAt(int childIndex) { return null; } public int getChildCount() { return 0; } /** * relative to tree, not indexes :p */ public int getIndex(TreeNode node) { return -1; } public void setParent(MutableTreeNode newParent) { parentNode = newParent; setParent(((IndexTreeNode)newParent).getId()); } public void setParent(final int parentId) { synchronized(db.dbLock) { Logger.info(this, "setParent("+Integer.toString(parentId)+")"); try { PreparedStatement st; st = db.getConnection().prepareStatement("UPDATE indexes "+ "SET parent = ? "+ "WHERE id = ?"); if (parentId >= 0) st.setInt(1, parentId); else st.setNull(1, Types.INTEGER); st.setInt(2, id); st.execute(); st.close(); if (parentId >= 0) { st = db.getConnection().prepareStatement("INSERT INTO indexParents (indexId, folderId) "+ " SELECT ?, parentId FROM folderParents "+ " WHERE folderId = ?"); st.setInt(1, id); st.setInt(2, parentId); st.execute(); st.close(); } /* else this parent has no parent ... :) */ st = db.getConnection().prepareStatement("INSERT INTO indexParents (indexId, folderId) "+ "VALUES (?, ?)"); st.setInt(1, id); if (parentId >= 0) st.setInt(2, parentId); else st.setNull(2, Types.INTEGER); st.execute(); st.close(); } catch(SQLException e) { Logger.error(this, "Error while changing parent : "+e.toString()); } } } /** * entry point */ public void removeFromParent() { Logger.info(this, "removeFromParent()"); ((IndexFolder)parentNode).remove(this); synchronized(db.dbLock) { PreparedStatement st; try { st = db.getConnection().prepareStatement("DELETE FROM indexParents "+ "WHERE indexId = ?"); st.setInt(1, id); st.execute(); st.close(); } catch(SQLException e) { Logger.error(this, "Error while removing the index: "+e.toString()); } } } public void remove(int index) { /* nothing to do */ } public void remove(MutableTreeNode node) { /* nothing to do */ } public void insert(MutableTreeNode child, int index) { /* nothing to do */ } public boolean isLeaf() { return true; } public void setUserObject(Object o) { rename(o.toString()); } public MutableTreeNode getTreeNode() { return this; } public void rename(final String name) { synchronized(db.dbLock) { try { final Connection c = db.getConnection(); PreparedStatement st; st = c.prepareStatement("UPDATE indexes SET displayName = ? WHERE id = ?"); st.setString(1, name); st.setInt(2, id); st.execute(); st.close(); } catch(final SQLException e) { Logger.error(this, "Unable to rename the index in '"+name+"', because: "+e.toString()); } } } public void delete() { removeFromParent(); synchronized(db.dbLock) { try { PreparedStatement st; purgeFileList(false); purgeLinkList(false); purgeCommentKeys(); st = db.getConnection().prepareStatement("DELETE FROM indexParents "+ "WHERE indexId = ?"); st.setInt(1, id); st.execute(); st.close(); Logger.notice(this, "DELETING AN INDEX"); st = db.getConnection().prepareStatement("DELETE FROM indexes WHERE id = ?"); st.setInt(1, id); st.execute(); st.close(); } catch(SQLException e) { Logger.error(this, "Unable to delete the index because : "+e.toString()); } } } /** * call purgeLinkList(false) */ public void purgeLinkList() { purgeLinkList(false); } public void purgeLinkList(boolean useDontDelete) { synchronized(db.dbLock) { try { final Connection c = db.getConnection(); final PreparedStatement st; if (!useDontDelete) st = c.prepareStatement("DELETE FROM links WHERE indexParent = ?"); else st = c.prepareStatement("DELETE FROM links WHERE indexParent = ? "+ "AND dontDelete = FALSE"); st.setInt(1, getId()); st.execute(); st.close(); } catch(final SQLException e) { Logger.error(this, "Unable to purge da list ! Exception: "+e.toString()); } } } /** * call purgeFileList(false) */ public void purgeFileList() { purgeFileList(false); } public void purgeFileList(boolean useDontDelete) { synchronized(db.dbLock) { try { final Connection c = db.getConnection(); final PreparedStatement st; if (!useDontDelete) st = c.prepareStatement("DELETE FROM files WHERE indexParent = ?"); else st = c.prepareStatement("DELETE FROM files WHERE indexParent = ? AND dontDelete = FALSE"); st.setInt(1, getId()); st.execute(); st.close(); } catch(final SQLException e) { Logger.error(this, "Unable to purge da list ! Exception: "+e.toString()); } } } public int getId() { return id; } public boolean loadData() { Logger.debug(this, "loadData()"); synchronized(db.dbLock) { try { PreparedStatement st = db.getConnection().prepareStatement("SELECT publicKey, "+ " revision, "+ " privateKey, "+ " publishPrivateKey, "+ " displayName, "+ " newRev, "+ " newComment, "+ " insertionDate "+ "FROM indexes "+ "WHERE id = ? LIMIT 1"); st.setInt(1, id); ResultSet set = st.executeQuery(); if (set.next()) { publicKey = set.getString("publicKey"); privateKey = set.getString("privateKey"); publishPrivateKey = set.getBoolean("publishPrivateKey"); rev = set.getInt("revision"); displayName = set.getString("displayName"); hasChanged = set.getBoolean("newRev"); newComment = set.getBoolean("newComment"); date = set.getDate("insertionDate"); st.close(); return true; } else { Logger.error(this, "Unable to find index "+Integer.toString(id)+" in the database ?!"); st.close(); return false; } } catch (final SQLException e) { Logger.error(this, "Unable to get public key because: "+e.toString()); } } return false; } public String getPublicKey() { if (publicKey == null) { Logger.debug(this, "getPublicKey() => loadData()"); loadData(); } if (!publicKey.endsWith(".frdx")) return publicKey+"/"+toString(false)+".frdx"; return publicKey; } public java.sql.Date getDate() { if (publicKey == null) { Logger.debug(this, "getDate() => loadData()"); loadData(); } return date; } public boolean isObsolete() { return FreenetURIHelper.isObsolete(getPublicKey()); } public int getRevision() { if (rev < 0) { Logger.debug(this, "getRevision() => loadData()"); loadData(); } return rev; } public String getPrivateKey() { if (publicKey == null) { /* we rely on the publicKey because the privateKey is not often availabe */ Logger.debug(this, "getPrivateKey() => loadData()"); loadData(); } return privateKey; } public boolean publishPrivateKey() { if (publicKey == null) { Logger.debug(this, "getPrivateKey() => loadData()"); loadData(); } return publishPrivateKey; } public void setPublishPrivateKey(boolean val) { try { synchronized(db.dbLock) { PreparedStatement st = db.getConnection().prepareStatement("UPDATE indexes "+ "SET publishPrivateKey = ? "+ "WHERE id = ?"); st.setBoolean(1, val); st.setInt(2, id); st.execute(); st.close(); } publishPrivateKey = val; } catch(SQLException e){ Logger.error(this, "Unable to set publishPrivateKey value because: "+e.toString()); } } public void setPublicKey(String publicKey) { int rev = FreenetURIHelper.getUSKRevision(publicKey); setPublicKey(publicKey, rev); } /** * Use directly this function only if you're sure that the rev is the same in the key * @param publicKey must be an USK */ public void setPublicKey(String publicKey, int rev) { this.publicKey = publicKey; this.rev = rev; synchronized(db.dbLock) { try { PreparedStatement st; st = db.getConnection().prepareStatement("UPDATE indexes "+ "SET publicKey = ?, revision = ? "+ "WHERE id = ?"); st.setString(1, publicKey); st.setInt(2, rev); st.setInt(3, id); st.execute(); st.close(); /* we update also all the links in the index with the private key */ st = db.getConnection().prepareStatement("SELECT links.id, links.publicKey "+ "FROM LINKS JOIN INDEXES ON links.indexParent = indexes.id "+ "WHERE indexes.privateKey IS NOT NULL AND LOWER(publicKey) LIKE ?"); st.setString(1, FreenetURIHelper.getComparablePart(publicKey)+"%"); ResultSet res = st.executeQuery(); PreparedStatement updateLinkSt; updateLinkSt = db.getConnection().prepareStatement("UPDATE links SET publicKey = ? WHERE id = ?"); while(res.next()) { String pubKey = res.getString("publicKey").replaceAll(".xml", ".frdx"); if (FreenetURIHelper.compareKeys(pubKey, publicKey)) { updateLinkSt.setString(1, publicKey); updateLinkSt.setInt(2, res.getInt("id")); updateLinkSt.execute(); } } st.close(); updateLinkSt.close(); } catch(SQLException e) { Logger.error(this, "Unable to set public Key because: "+e.toString()); } } } public void setPrivateKey(String privateKey) { this.privateKey = privateKey; setPrivateKey(db, id, privateKey); } public static void setPrivateKey(Hsqldb db, int indexId, String privateKey) { if (privateKey != null && !FreenetURIHelper.isAKey(privateKey)) privateKey = null; synchronized(db.dbLock) { try { PreparedStatement st; st = db.getConnection().prepareStatement("UPDATE indexes "+ "SET privateKey = ? "+ "WHERE id = ?"); if (privateKey != null) st.setString(1, privateKey); else st.setNull(1, Types.VARCHAR); st.setInt(2, indexId); st.execute(); st.close(); } catch(SQLException e) { Logger.error(new Index(), "Unable to set private Key because: "+e.toString()); } } } public String getRealName() { if (realName != null) return realName; synchronized(db.dbLock) { try { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT originalName FROM indexes WHERE id = ?"); st.setInt(1, id); ResultSet set = st.executeQuery(); if (set.next()) { realName = set.getString("originalName"); st.close(); return realName; } else { Logger.error(this, "Unable to get index real name: not found"); st.close(); return null; } } catch(SQLException e) { Logger.error(this, "Unable to get real index name: "+e.toString()); } } return null; } public String toString() { return toString(true); } public String toString(boolean withRev) { if (displayName == null || rev < 0) { Logger.debug(this, "toString() => loadData()"); loadData(); } if (withRev) { if (rev > 0 || (rev == 0 && privateKey == null)) return displayName + " (r"+Integer.toString(rev)+")"; else { if (rev > 0) return displayName+" ["+I18n.getMessage("thaw.plugin.index.nonInserted")+"]"; else return displayName; } } else return displayName; } private IndexTree indexTree = null; public int insertOnFreenet(Observer o, IndexBrowserPanel indexBrowser, FCPQueueManager queueManager) { String privateKey = getPrivateKey(); String publicKey = getPublicKey(); int rev = getRevision(); if (indexBrowser != null && indexBrowser.getMainWindow() != null) { indexTree = indexBrowser.getIndexTree(); synchronized(db.dbLock) { try { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT id FROM links where indexParent = ? LIMIT 1"); st.setInt(1, id); ResultSet set = st.executeQuery(); if (!set.next()) { st.close(); /* no link ?! we will warn the user */ int ret = JOptionPane.showOptionDialog(indexBrowser.getMainWindow().getMainFrame(), I18n.getMessage("thaw.plugin.index.indexWithNoLink").replaceAll("\\?", toString(false)), I18n.getMessage("thaw.warning.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null); if (ret == JOptionPane.CLOSED_OPTION || ret == JOptionPane.NO_OPTION) { return 0; } } else st.close(); } catch(SQLException e) { Logger.error(this, "Error while checking the link number before insertion : "+e.toString()); } } if (getCategory() == null) { int ret = JOptionPane.showOptionDialog(indexBrowser.getMainWindow().getMainFrame(), I18n.getMessage("thaw.plugin.index.noCategory").replaceAll("\\?", toString(false)), I18n.getMessage("thaw.warning.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null); if (ret == JOptionPane.CLOSED_OPTION || ret == JOptionPane.NO_OPTION) { return 0; } } } /* Let's hope that users are not stupid * and won't insert too much revisions at once. */ /* if (indexBrowser != null && indexBrowser.getIndexTree() != null && indexBrowser.getIndexTree().isIndexUpdating(this)) { Logger.notice(this, "A transfer is already running !"); return 0; } */ if (!FreenetURIHelper.isAKey(publicKey) || !FreenetURIHelper.isAKey(privateKey)) { /* non modifiable */ Logger.notice(this, "Tried to insert an index for which we don't have the private key ..."); return 0; } String tmpdir = System.getProperty("java.io.tmpdir"); if (tmpdir == null) tmpdir = ""; else tmpdir = tmpdir + java.io.File.separator; java.io.File targetFile = new java.io.File(tmpdir + getRealName() +".frdx"); Logger.info(this, "Generating index ..."); IndexParser parser = new IndexParser(this); if (!parser.generateXML(targetFile.getAbsolutePath())) return 0; FCPClientPut put; if(targetFile.exists()) { Logger.info(this, "Inserting new version"); String key = FreenetURIHelper.changeUSKRevision(publicKey, rev, 1); rev++; put = new FCPClientPut(targetFile, FCPClientPut.KEY_TYPE_SSK, rev, realName, privateKey, 2 /*priority*/, true /* global queue */, FCPClientPut.PERSISTENCE_FOREVER, true, /* doCompress */ -1); /* compression codec */ put.setMetadata("ContentType", "application/x-freenet-index"); if (indexBrowser != null && indexBrowser.getIndexTree() != null) indexBrowser.getIndexTree().addUpdatingIndex(this); queueManager.addQueryToTheRunningQueue(put); put.addObserver(this); this.addObserver(o); setPublicKey(key, rev); try { synchronized(db.dbLock) { PreparedStatement st; String query = "UPDATE # SET dontDelete = FALSE WHERE indexParent = ?"; st = db.getConnection().prepareStatement(query.replaceFirst("#", "files")); st.setInt(1, id); st.execute(); st.close(); st = db.getConnection().prepareStatement(query.replaceFirst("#", "links")); st.setInt(1, id); st.execute(); st.close(); } } catch(SQLException e) { Logger.error(this, "Error while reseting dontDelete flags: "+e.toString()); } } else { Logger.warning(this, "Index not generated !"); return 0; } return 1; } public int downloadFromFreenet(Observer o, IndexTree tree, FCPQueueManager queueManager) { return downloadFromFreenet(o, tree, queueManager, -1); } /** * if true, when the transfer will finish, the index public key will be updated */ private boolean rewriteKey = true; private FCPQueueManager queueManager; private boolean fetchingNegRev = false; private boolean mustFetchNegRev = false; public int downloadFromFreenet(Observer o, IndexTree tree, FCPQueueManager queueManager, int specificRev) { this.queueManager = queueManager; indexTree = tree; rewriteKey = true; fetchingNegRev = false; mustFetchNegRev = false; if (config != null && config.getValue("indexFetchNegative") != null) mustFetchNegRev = Boolean.valueOf(config.getValue("indexFetchNegative")).booleanValue(); boolean v = realDownloadFromFreenet(specificRev); if (o != null) this.addObserver(o); return (v ? 1 : 0); } protected boolean realDownloadFromFreenet(int specificRev) { FCPClientGet clientGet; String publicKey; int rev = getRevision(); publicKey = getPublicKey(); String privateKey = getPrivateKey(); if (rev <= 0 && privateKey != null) { Logger.error(this, "Can't update an non-inserted index !"); return false; } if (indexTree != null && indexTree.isIndexUpdating(this)) { Logger.notice(this, "A transfer is already running !"); return false; } if (publicKey == null) { Logger.error(this, "No public key !! Can't get the index !"); return false; } String key; if (specificRev >= 0) { key = FreenetURIHelper.convertUSKtoSSK(publicKey); key = FreenetURIHelper.changeSSKRevision(key, specificRev, 0); rewriteKey = false; } else { key = publicKey; rewriteKey = true; } if (rev < 0) rewriteKey = false; Logger.info(this, "Updating index ..."); if (key.startsWith("USK")) { int daRev = FreenetURIHelper.getUSKRevision(key); if ((fetchingNegRev && daRev > 0) || (!fetchingNegRev && daRev < 0)) { daRev = -1 * daRev; key = FreenetURIHelper.changeUSKRevision(key, daRev, 0); } } Logger.debug(this, "Key asked: "+key); clientGet = new FCPClientGet(key, 2, /* <= priority */ FCPClientGet.PERSISTENCE_UNTIL_DISCONNECT, false, /* <= globalQueue */ 10, /* maxRetries */ System.getProperty("java.io.tmpdir"), /* destination directory */ MAX_SIZE, /* max size */ true /* <= noDDA */); /* * These requests are usually quite fast, and don't consume too much * of bandwith / CPU. So we can skip the queue and start immediatly * (and like this, they won't appear in the queue) */ clientGet.start(queueManager); if (indexTree != null) indexTree.addUpdatingIndex(this); clientGet.addObserver(this); return true; } public void useTrayIconToNotifyNewRev() { if (indexTree == null) return; String announcement = I18n.getMessage("thaw.plugin.index.newRev"); try { announcement = announcement.replaceAll("X", toString(false)); announcement = announcement.replaceAll("Y", Integer.toString(getRevision())); thaw.plugins.TrayIcon.popMessage(indexTree.getIndexBrowserPanel().getCore().getPluginManager(), I18n.getMessage("thaw.plugin.index.browser"), announcement); } catch (Exception e) { /* it can happen with some index name (probably because of characters like '$' */ Logger.notice(this, "Can't popup to notify an index update because : "+e.toString()); } } public void update(Observable o, Object param) { if (o instanceof FCPClientGet) { FCPClientGet get = (FCPClientGet)o; if (get.isFinished()) { get.deleteObserver(this); if (get.isSuccessful()) { successful = true; String key = get.getFileKey(); int oldRev = rev; int newRev = FreenetURIHelper.getUSKRevision(key); if (rewriteKey) { setPublicKey(key, newRev); } if (oldRev < newRev || isNew) { setHasChangedFlag(true); useTrayIconToNotifyNewRev(); } isNew = false; String path = get.getPath(); if (path != null) { IndexParser parser = new IndexParser(this); parser.loadXML(path); if (!fetchingNegRev && mustFetchNegRev && getCommentPublicKey() != null) { final java.io.File fl = new java.io.File(path); fl.delete(); setChanged(); notifyObservers(); fetchingNegRev = true; realDownloadFromFreenet(-1); return; } boolean loadComm = true; if (config != null && config.getValue("indexFetchComments") != null) loadComm = Boolean.valueOf(config.getValue("indexFetchComments")).booleanValue(); if (getCommentPublicKey() != null && loadComm) { loadComments(queueManager); } else if (indexTree != null) indexTree.removeUpdatingIndex(this); } else Logger.error(this, "No path specified in transfer ?!"); } else { /* if not successful */ successful = false; indexTree.removeUpdatingIndex(this); } } } if (o instanceof FCPClientPut) { FCPClientPut put = ((FCPClientPut)o); /* TODO : check if it's successful, else merge if it's due to a collision */ if (put.isFinished()) { put.deleteObserver(this); if (indexTree != null) indexTree.removeUpdatingIndex(this); if (put.isSuccessful()) { try { synchronized(db.dbLock) { PreparedStatement st; Calendar cal= Calendar.getInstance(); java.sql.Date dateSql = new java.sql.Date(cal.getTime().getTime() ); st = db.getConnection().prepareStatement("UPDATE indexes "+ "SET insertionDate = ? "+ "WHERE id = ?"); st.setDate(1, dateSql); st.setInt(2, id); st.execute(); st.close(); } } catch(SQLException e) { Logger.error(this, "Error while updating the insertion date : "+e.toString()); } } else { if (put.getRevision() == rev) setPublicKey(publicKey, rev-1); } } } if (o instanceof Comment) { Comment c = (Comment)o; if (c.exists()) { nmbFailedCommentFetching = 0; if (c.isNew() && c.isValid()) { Logger.info(this, "New comment !"); setNewCommentFlag(true); setChanged(); notifyObservers(); } } else { nmbFailedCommentFetching++; } c.deleteObserver(this); if (nmbFailedCommentFetching > COMMENT_FETCHING_RUNNING_AT_THE_SAME_TIME +1) { if (indexTree != null) { Logger.info(this, "All the comments should be fetched"); indexTree.removeUpdatingIndex(this); } } else { lastCommentRev++; Comment comment = new Comment(db, this, lastCommentRev, null, null); comment.addObserver(this); comment.fetchComment(queueManager); } } if (o instanceof FCPTransferQuery) { FCPTransferQuery transfer = (FCPTransferQuery)o; if (transfer.isFinished()) { String path = transfer.getPath(); final java.io.File fl = new java.io.File(path); fl.delete(); setChanged(); notifyObservers(); } } } /** * call purgeIndex(true) */ public void purgeIndex() { purgeIndex(true); } public void purgeIndex(boolean useDontDelete) { purgeFileList(useDontDelete); purgeLinkList(useDontDelete); if (!useDontDelete) purgeCommentKeys(); } public void setInsertionDate(java.util.Date date) { try { synchronized(db.dbLock) { java.sql.Date dateSql = null; dateSql = new java.sql.Date(date.getTime()); PreparedStatement st = db.getConnection().prepareStatement("UPDATE indexes "+ "SET insertionDate = ? "+ "WHERE id = ?"); st.setDate(1, dateSql); st.setInt(2, id); st.execute(); st.close(); } } catch(SQLException e) { Logger.error(this, "Error while updating index insertion date: "+e.toString()); } } ////// Comments black list ////// public Vector getCommentBlacklistedRev() { Vector v = new Vector(); try { synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT rev "+ "FROM indexCommentBlackList "+ "WHERE indexId = ?"); st.setInt(1, id); ResultSet set = st.executeQuery(); while (set.next()) { v.add(new Integer(set.getInt("rev"))); } st.close(); } } catch(SQLException e) { Logger.error(this, "Unable to get comment black list because: "+e.toString()); } return v; } public void addBlackListedRev(int rev) { try { synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("INSERT into indexCommentBlackList (rev, indexId) VALUES (?, ?)"); st.setInt(1, rev); st.setInt(2, id); st.execute(); st.close(); } } catch(SQLException e) { Logger.error(this, "Error while adding element to the blackList: "+e.toString()); } } ////// FILE LIST //////// public File[] getFileList() { return getFileList(null, false); } public File[] getFileList(String columnToSort, boolean asc) { synchronized(db.dbLock) { try { java.util.LinkedList files = new java.util.LinkedList(); PreparedStatement st; if (columnToSort == null) { st = db.getConnection().prepareStatement("SELECT id, filename, publicKey, localPath, mime, size "+ "FROM files WHERE indexParent = ?"); } else { st = db.getConnection().prepareStatement("SELECT id, filename, publicKey, localPath, mime, size "+ "FROM files WHERE indexParent = ? ORDER by "+ columnToSort + (asc == true ? "" : " DESC")); } st.setInt(1, id); ResultSet rs = st.executeQuery(); while(rs.next()) { int file_id = rs.getInt("id"); String filename = rs.getString("filename"); String file_publicKey = rs.getString("publicKey"); String lp = rs.getString("localPath"); java.io.File localPath = (lp == null ? null : new java.io.File(lp)); String mime = rs.getString("mime"); long size = rs.getLong("size"); thaw.plugins.index.File file = new thaw.plugins.index.File(db, file_id, filename, file_publicKey, localPath, mime, size, id, this); files.add(file); } st.close(); return (File[])files.toArray(new File[0]); } catch(SQLException e) { Logger.error(this, "SQLException while getting file list: "+e.toString()); } } return null; } public boolean addFile(String key, long size, String mime) { try { synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("INSERT INTO files " + "(filename, publicKey, localPath, mime, size, category, indexParent) " + "VALUES (?, ?, NULL, ?, ?, NULL, ?)"); String filename = FreenetURIHelper.getFilenameFromKey(key); if (filename == null) filename = key; st.setString(1, filename); st.setString(2, key); st.setString(3, mime); st.setLong(4, size); st.setInt(5, id); st.execute(); st.close(); return true; } } catch(SQLException e) { Logger.error(this, "Error while adding file to index '"+toString()+"' : "+e.toString()); } return false; } //// LINKS //// public Link[] getLinkList() { return getLinkList(null, false); } public Link[] getLinkList(String columnToSort, boolean asc) { synchronized(db.dbLock) { try { java.util.LinkedList links = new java.util.LinkedList(); PreparedStatement st; st = db.getConnection().prepareStatement("SELECT links.id AS id, " + " links.publicKey AS publicKey, "+ " links.blackListed AS blacklisted," + " links.indexParent AS indexParent, "+ " categories.name AS categoryName "+ " FROM links LEFT OUTER JOIN categories "+ " ON links.category = categories.id "+ "WHERE links.indexParent = ?"); st.setInt(1, id); ResultSet res = st.executeQuery(); while(res.next()) { Link l = new Link(db, res.getInt("id"), res.getString("publicKey"), res.getString("categoryName"), res.getBoolean("blackListed"), this); links.add(l); } st.close(); return (Link[])links.toArray(new Link[0]); } catch(SQLException e) { Logger.error(this, "SQLException while getting link list: "+e.toString()); } } return null; } public static String getNameFromKey(final String key) { String name = null; name = FreenetURIHelper.getFilenameFromKey(key); if (name == null) return null; /* quick and dirty */ name = name.replaceAll(".xml", ""); name = name.replaceAll(".frdx", ""); return name; } public boolean addLink(String key, String category) { try { if (key == null) /* it was the beginning of the index */ return true; key = key.trim(); boolean blackListed = (BlackList.isBlackListed(db, key) >= 0); synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("INSERT INTO links "+ "(publicKey, mark, comment, "+ "indexParent, indexTarget, blackListed, category) "+ "VALUES (?, 0, ?, ?, NULL, ?, ?)"); st.setString(1, key); st.setString(2, "No comment"); /* comment not used at the moment */ st.setInt(3, id); st.setBoolean(4, blackListed); if (category != null) st.setInt(5, getCategoryId(category)); else st.setNull(5, Types.INTEGER); st.execute(); st.close(); return true; } } catch(SQLException e) { Logger.error(this, "Error while adding link to index '"+toString()+"' : "+e.toString()); } return false; } public String findTheLatestKey(String linkKey) { synchronized(db.dbLock) { try { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT publicKey, revision "+ "FROM indexes "+ "WHERE publicKey LIKE ?"); st.setString(1, FreenetURIHelper.getComparablePart(linkKey)); ResultSet set = st.executeQuery(); while (set.next()) { /* we will assume that we have *always* the latest version of the index */ String oKey = set.getString("publicKey"); if (FreenetURIHelper.compareKeys(oKey, linkKey)) { String key = FreenetURIHelper.changeUSKRevision(oKey, set.getInt("revision"), 0); st.close(); return key; } } st.close(); } catch(SQLException e) { Logger.error(this, "Can't find the latest key of a link because : "+e.toString()); } } return linkKey; } public boolean isModifiable() { if (getPrivateKey() != null) return true; return false; } public static int isAlreadyKnown(Hsqldb db, String key) { return isAlreadyKnown(db, key, false); } /** * @return the index id if found ; -1 else */ public static int isAlreadyKnown(Hsqldb db, String key, boolean strict) { if (key.length() < 40) { Logger.error(new Index(), "isAlreadyKnown(): Invalid key: "+key); return -1; } key = key.replaceAll(".xml", ".frdx"); synchronized(db.dbLock) { try { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT id, publicKey from indexes WHERE LOWER(publicKey) LIKE ?" + (strict ? "" : " LIMIT 1")); st.setString(1, FreenetURIHelper.getComparablePart(key) +"%"); ResultSet res = st.executeQuery(); if (strict) { while(res.next()) { String pubKey = res.getString("publicKey").replaceAll(".xml", ".frdx"); if (FreenetURIHelper.compareKeys(pubKey, key)) { int r = res.getInt("id"); st.close(); return r; } } st.close(); return -1; } else { if (!res.next()) { st.close(); return -1; } int r = res.getInt("id"); st.close(); return r; } } catch(final SQLException e) { Logger.error(new Index(), "isAlreadyKnown: Unable to check if link '"+key+"' point to a know index because: "+e.toString()); } } return -1; } public void forceFlagsReload() { Logger.verbose(this, "forceReload() => loadData()"); loadData(); } public boolean hasChanged() { if (publicKey == null) { Logger.debug(this, "hasChanged() => loadData()"); loadData(); } return hasChanged; } public boolean hasNewComment() { if (publicKey == null) { Logger.debug(this, "hasNewComment() => loadData()"); loadData(); } return newComment; } public boolean setHasChangedFlagInMem(boolean flag) { hasChanged = flag; return true; } public boolean setNewCommentFlagInMem(boolean flag) { newComment = flag; return true; } /** * @return true if a change was done */ public boolean setHasChangedFlag(boolean flag) { setHasChangedFlagInMem(flag); synchronized(db.dbLock) { try { PreparedStatement st; st = db.getConnection().prepareStatement("UPDATE indexes SET newRev = ? "+ "WHERE id = ?"); st.setBoolean(1, flag); st.setInt(2, id); if (st.executeUpdate() > 0) { st.close(); return true; } st.close(); return false; } catch(SQLException e) { Logger.error(this, "Unable to change 'hasChanged' flag because: "+e.toString()); } } return false; } /** * @return true if a change was done */ public boolean setNewCommentFlag(boolean flag) { setNewCommentFlagInMem(flag); synchronized(db.dbLock) { try { PreparedStatement st; st = db.getConnection().prepareStatement("UPDATE indexes SET newComment = ? "+ "WHERE id = ?"); st.setBoolean(1, flag); st.setInt(2, id); if (st.executeUpdate() > 0) { st.close(); return true; } st.close(); return false; } catch(SQLException e) { Logger.error(this, "Unable to change 'newComment' flag because: "+e.toString()); } } return false; } public Element do_export(Document xmlDoc, boolean withContent) { Element e = xmlDoc.createElement("fullIndex"); e.setAttribute("displayName", toString(false)); e.setAttribute("publicKey", getPublicKey()); if (getPrivateKey() != null) e.setAttribute("privateKey", getPrivateKey()); if (withContent) { new IndexParser(this).fillInRootElement(e, xmlDoc); } return e; } public boolean equals(Object o) { if (o == null || !(o instanceof Index)) return false; if (((Index)o).getId() == getId()) return true; return false; } /** * @return an SSK@ */ public String getCommentPublicKey() { try { synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT publicKey FROM indexCommentKeys WHERE indexId = ? LIMIT 1"); st.setInt(1, id); ResultSet set = st.executeQuery(); String r = null; if (set != null && set.next()) { r = set.getString("publicKey"); } st.close(); return r; } } catch(SQLException e) { Logger.error(this, "Unable to get comment public key because : "+e.toString()); } return null; } /** * @return an SSK@ */ public String getCommentPrivateKey() { try { synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT privateKey FROM indexCommentKeys WHERE indexId = ? LIMIT 1"); st.setInt(1, id); ResultSet set = st.executeQuery(); String r = null; if (set != null && set.next()) r = set.getString("privateKey"); st.close(); return r; } } catch(SQLException e) { Logger.error(this, "Unable to get comment public key because : "+e.toString()); } return null; } /** * Will also purge comments ! */ public void purgeCommentKeys() { try { synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("DELETE FROM indexCommentBlackList WHERE indexId = ?"); st.setInt(1, id); st.execute(); st.close(); st = db.getConnection().prepareStatement("DELETE FROM indexComments WHERE indexId = ?"); st.setInt(1, id); st.execute(); st.close(); st = db.getConnection().prepareStatement("DELETE FROM indexCommentKeys WHERE indexId = ?"); st.setInt(1, id); st.execute(); st.close(); } } catch(SQLException e) { Logger.error(this, "Unable to purge comment keys, because : "+e.toString()); } } /** * will reset the comments ! */ public void setCommentKeys(String publicKey, String privateKey) { String oldPubKey = getCommentPublicKey(); String oldPrivKey = getCommentPrivateKey(); if ( ((publicKey == null && oldPubKey == null) || (publicKey != null && publicKey.equals(oldPubKey))) && ((privateKey == null && oldPrivKey == null) || (privateKey != null && privateKey.equals(oldPrivKey))) ) return; /* same keys => no change */ purgeCommentKeys(); try { synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("INSERT INTO indexCommentKeys (publicKey, privateKey, indexId) VALUES (?, ?, ?)"); st.setString(1, publicKey); st.setString(2, privateKey); st.setInt(3, id); st.execute(); st.close(); } } catch(SQLException e) { Logger.error(this, "Unable to set comment keys, because : "+e.toString()); } } protected class CommentKeyRegenerator implements Observer { private FCPGenerateSSK sskGenerator; public CommentKeyRegenerator(FCPQueueManager queueManager) { sskGenerator = new FCPGenerateSSK(); sskGenerator.addObserver(this); sskGenerator.start(queueManager); } public void update(Observable o, Object param) { if (o instanceof FCPGenerateSSK) { setCommentKeys(((FCPGenerateSSK)o).getPublicKey(), ((FCPGenerateSSK)o).getPrivateKey()); } } } public void regenerateCommentKeys(FCPQueueManager queueManager) { new CommentKeyRegenerator(queueManager); } /** * @return true if the public key to fetch the comments is available */ public boolean canHaveComments() { return (getCommentPublicKey() != null); } public boolean postComment(FCPQueueManager queueManager, MainWindow mainWindow, Identity author, String msg) { if (getCommentPrivateKey() == null) { return false; } Comment comment = new Comment(db, this, -1, author, msg); return comment.insertComment(queueManager, mainWindow); } public void loadComments(FCPQueueManager queueManager) { if (getCommentPublicKey() == null) return; if (queueManager == null) { Logger.warning(this, "Can't load comments ! QueueManager is not set for this index !"); return; } for (lastCommentRev = 0 ; lastCommentRev < COMMENT_FETCHING_RUNNING_AT_THE_SAME_TIME; lastCommentRev++) { Comment comment = new Comment(db, this, lastCommentRev, null, null); comment.addObserver(this); comment.fetchComment(queueManager); } } public Vector getComments() { return getComments(true); } public Vector getComments(boolean asc) { try { synchronized(db.dbLock) { Vector comments = new Vector(); PreparedStatement st; st = db.getConnection().prepareStatement("SELECT authorId, text, rev "+ "FROM indexComments WHERE indexId = ? ORDER BY rev" + (asc ? "" : " DESC")); st.setInt(1, id); ResultSet set = st.executeQuery(); while(set.next()) comments.add(new Comment(db, this, set.getInt("rev"), Identity.getIdentity(db, set.getInt("authorId")), set.getString("text"))); st.close(); if (comments.size() == 0) Logger.notice(this, "No comment for this index"); else Logger.info(this, Integer.toString(comments.size())+ " comments for this index"); return comments; } } catch(SQLException e) { Logger.error(this, "Error while fetching comment list : "+e.toString()); } return null; } public int getNmbComments() { try { int nmb = 0; synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT count(indexComments.id) "+ "FROM indexComments "+ "WHERE indexComments.indexId = ? "+ "AND indexComments.rev NOT IN "+ " (SELECT indexCommentBlackList.rev "+ " FROM indexCommentBlackList "+ " WHERE indexCommentBlackList.indexId = ?)"); st.setInt(1, id); st.setInt(2, id); ResultSet set = st.executeQuery(); if (set.next()) nmb = set.getInt(1); st.close(); return nmb; } } catch(SQLException e) { Logger.error(this, "Error while fetching comment list : "+e.toString()); } return 0; } /* The user who is able to have so much depth in its tree * is crazy. */ public final static int MAX_DEPTH = 128; public TreePath getTreePath(IndexTree tree) { int[] folderIds = new int[MAX_DEPTH]; for (int i = 0 ; i < folderIds.length ; i++) folderIds[i] = -1; synchronized(db.dbLock) { try { /* we find the id of the parents */ PreparedStatement st = db.getConnection().prepareStatement("SELECT folderId FROM indexParents "+ "WHERE indexId = ? LIMIT 1"); st.setInt(1, id); ResultSet res = st.executeQuery(); if (!res.next()) { Logger.error(this, "Can't find the index "+Integer.toString(id)+"in the db! The tree is probably broken !"); st.close(); return null; } int i = 0; do { int j = res.getInt("folderId"); if (j != 0) /* root */ folderIds[i] = j; i++; } while(res.next()); st.close(); int nmb_folders = i+1; /* i + root */ Object[] path = new Object[nmb_folders + 1]; /* folders + the index */ for (i = 0 ; i < path.length ; i++) path[i] = null; path[0] = indexTree.getRoot(); for (i = 1 ; i < nmb_folders ; i++) { IndexFolder folder = null; for (int j = 0 ; folder == null && j < folderIds.length && folderIds[j] != -1 ; j++) { folder = ((IndexFolder)path[i-1]).getChildFolder(folderIds[j], false); } if (folder == null) break; path[i] = folder; } if (i >= 2) path[i-1] = ((IndexFolder)path[i-2]).getChildIndex(id, false); else path[1] = indexTree.getRoot().getChildIndex(id, false); int non_null_elements = 0; /* we may have null elements if the tree wasn't fully loaded for this path */ for (i = 0 ; i < path.length ; i++) { if (path[i] == null) break; } non_null_elements = i; if (non_null_elements != nmb_folders) { /* we eliminate the null elements */ Object[] new_path = new Object[non_null_elements]; for (i = 0 ; i < non_null_elements; i++) new_path[i] = path[i]; path = new_path; } return new TreePath(path); } catch(SQLException e) { Logger.error(this, "Error while getting index tree path : "+e.toString()); } } return null; } public String getClientVersion() { return ("Thaw "+Main.VERSION); } /** * @return -1 if none */ public int getCategoryId() { try { synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT categoryId "+ "FROM indexes "+ "WHERE id = ? LIMIT 1"); st.setInt(1, id); ResultSet set = st.executeQuery(); if (!set.next()) { st.close(); return -1; } Object o = set.getObject("categoryId"); if (o == null) { st.close(); return -1; } int i = set.getInt("categoryId"); st.close(); return i; } } catch(SQLException e) { Logger.error(this, "Unable to get the category of the index because : "+ e.toString()); } return -1; } public String getCategory() { try { synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT categories.name AS name "+ "FROM categories INNER JOIN indexes "+ " ON categories.id = indexes.categoryId "+ "WHERE indexes.id = ? LIMIT 1"); st.setInt(1, id); ResultSet set = st.executeQuery(); if (!set.next()) { st.close(); return null; } String r = set.getString("name").toLowerCase(); st.close(); return r; } } catch(SQLException e) { Logger.error(this, "Unable to get the category of the index because : "+ e.toString()); } return null; } public static String cleanUpCategoryName(String category) { if (category == null) return null; category = category.trim(); category = category.toLowerCase(); String oldCat; do { oldCat = category; category = category.replaceAll("//", "/"); } while(!oldCat.equals(category)); if ("".equals(category)) return null; return category; } /** * create it if it doesn't exist */ protected int getCategoryId(String cat) { cat = cleanUpCategoryName(cat); if (cat == null) return -1; try { synchronized(db.dbLock) { PreparedStatement st; ResultSet set; int catId = 1; st = db.getConnection().prepareStatement("SELECT id FROM categories "+ "WHERE name = ? LIMIT 1"); st.setString(1, cat); set = st.executeQuery(); /* if it doesn't exist, we create it */ if (!set.next()) { st = db.getConnection().prepareStatement("SELECT id FROM categories "+ "ORDER by id DESC LIMIT 1"); set = st.executeQuery(); if (set.next()) catId = set.getInt("id")+1; st.close(); /* insertion */ st = db.getConnection().prepareStatement("INSERT INTO categories "+ "(id, name) VALUES (?, ?)"); st.setInt(1, catId); st.setString(2, cat); st.execute(); st.close(); return catId; } else { /* else we return the existing id */ int i = set.getInt("id"); st.close(); return i; } } } catch(SQLException e) { Logger.error(this, "Can't create/find the category '"+cat+"'"); } return -1; } /** * create it if it doesn't exist */ public void setCategory(String category) { cleanUpCategoryName(category); if (category == null || "".equals(category)) return; try { synchronized(db.dbLock) { int catId = getCategoryId(category); if (catId < 0) return; /* set the categoryId of the index */ PreparedStatement st = db.getConnection().prepareStatement("UPDATE indexes SET categoryId = ? "+ "WHERE id = ?"); st.setInt(1, catId); st.setInt(2, id); st.execute(); st.close(); } } catch(SQLException e) { Logger.error(this, "Can't set the category because : "+e.toString()); } } public boolean downloadSuccessful() { return successful; } public void setClientVersion(String str) { /* only used if it's the Thaw index who was updated */ final String thawIndexPart = FreenetURIHelper.getComparablePart(thaw.plugins.IndexBrowser.DEFAULT_INDEXES[0]); final String thisIndexPart = FreenetURIHelper.getComparablePart(getPublicKey()); if (!thawIndexPart.equals(thisIndexPart)) return; try { if (!str.startsWith("Thaw ")) { /* not made with Thaw ?! */ Logger.notice(this, "Can't parse the Thaw version in the index '"+toString(false)+"' ?!"); return; } str = str.substring(5); int spacePos = -1; if ( (spacePos = str.indexOf(" ")) < 0) { /* hu ? */ Logger.notice(this, "Can't parse the Thaw version in the index '"+toString(false)+"' ?!"); return; } str = str.substring(0, spacePos); String[] numbers = str.split("\\."); int major = Integer.parseInt(numbers[0]); int minor = Integer.parseInt(numbers[1]); int update = Integer.parseInt(numbers[2]); boolean mustPopup = false; if (major > Main._major) mustPopup = true; else if (major == Main._major) { if (minor > Main._minor) mustPopup = true; else if (update > Main._update) mustPopup = true; } if (mustPopup) { String newVersion = Integer.toString(major)+"."+ Integer.toString(minor)+"."+ Integer.toString(update); /* quick and dirty way to warn the user */ Logger.warning(this, I18n.getMessage("thaw.plugins.index.newThawVersion").replaceAll("X", newVersion)); } } catch(Exception e) { Logger.notice(this, "Unable to parse the client string of the index '"+toString(false)+ "' because : "+e.toString()); e.printStackTrace(); } } }