package thaw.plugins.miniFrost.frostKSK; import java.util.Vector; import java.util.Observer; import java.util.Observable; import java.sql.*; import java.util.Date; import java.util.Calendar; import thaw.core.Logger; import thaw.core.I18n; import thaw.core.ThawThread; import thaw.core.ThawRunnable; import thaw.plugins.Hsqldb; import thaw.plugins.signatures.Identity; import thaw.plugins.miniFrost.interfaces.Board; import thaw.plugins.miniFrost.interfaces.Message; import thaw.plugins.miniFrost.interfaces.Draft; public class KSKBoard extends Observable implements Board, ThawRunnable, Observer { public final static int MAX_DOWNLOADS_AT_THE_SAME_TIME = 5; public final static int MAX_FAILURES_IN_A_ROW = 5; public final static int DAYS_BEFORE_THE_LAST_REFRESH = 1; public final static int MIN_DAYS_IN_THE_PAST = 5; public final static int MAX_DAYS_IN_THE_PAST = 5; public final static int MIN_DAYS_IN_THE_FUTURE = 1; public final static int MAX_DAYS_IN_THE_FUTURE = 1; /* not really used */ private int id; private String name; /** * last successful & finished one */ private Date lastUpdate; private KSKBoardFactory factory; private boolean refreshing; private KSKBoard() { } public KSKBoard(KSKBoardFactory factory, int id, String name, Date lastUpdate) { this.id = id; this.name = name; this.factory = factory; this.lastUpdate = lastUpdate; refreshing = false; } public Vector getMessages(String[] keywords, int orderBy, boolean desc, boolean archived, boolean read, boolean unsigned, int minTrustLevel) { return getMessages(id, factory, this, keywords, orderBy, desc, archived, read, unsigned, minTrustLevel, false); } protected static Vector getMessages(int id, KSKBoardFactory factory, KSKBoard board, String[] keywords, int orderBy, boolean desc, boolean archived, boolean read, boolean unsigned, int minTrustLevel, boolean allBoards) { String orderColumn; if (orderBy == Board.ORDER_SUBJECT) orderColumn = "LOWER(frostKSKMessages.subject)"; else if (orderBy == Board.ORDER_SENDER) orderColumn = "LOWER(frostKSKMessages.nick)"; else orderColumn = "frostKSKMessages.date"; if (desc) orderColumn += " DESC"; String whereBase = "WHERE true AND "; if (!allBoards) { whereBase = "WHERE frostKSKMessages.boardId = ? AND "; } String archivedStr = " true "; if (!archived) archivedStr = "frostKSKMessages.archived = FALSE "; String readStr = ""; if (!read) readStr = " AND frostKSKMessages.read = FALSE "; String keywordsStr = ""; if (keywords != null) { for (int i = 0 ; i < keywords.length ; i++) { keywordsStr += " AND (LOWER(frostKSKMessages.subject) LIKE ? "+ " OR LOWER(frostKSKMessages.content) LIKE ? "+ " OR LOWER(frostKSKMessages.nick) LIKE ?)"; } } String trustLvlStr; if (unsigned) trustLvlStr = " AND (signatures.trustLevel IS NULL "+ " OR signatures.trustLevel >= "+Integer.toString(minTrustLevel)+") "; else trustLvlStr = " AND signatures.trustLevel >= "+Integer.toString(minTrustLevel)+" "; Vector v = new Vector(); try { Hsqldb db = factory.getDb(); synchronized(db.dbLock) { PreparedStatement st; String query = "SELECT frostKSKMessages.id, "+ " frostKSKMessages.msgId, "+ " frostKSKMessages.inReplyToId, "+ " frostKSKMessages.subject, "+ " frostKSKMessages.nick, "+ " frostKSKMessages.sigId, "+ " frostKSKMessages.date, "+ " frostKSKMessages.rev, "+ " frostKSKMessages.read, "+ " frostKSKMessages.archived, "+ " frostKSKMessages.boardId, "+ " frostKSKMessages.encryptedFor, "+ " signatures.nickName, "+ " signatures.publicKey, "+ " signatures.privateKey, "+ " signatures.isDup, "+ " signatures.trustLevel "+ "FROM frostKSKMessages LEFT OUTER JOIN signatures "+ " ON frostKSKMessages.sigId = signatures.id "+ whereBase+ archivedStr+ readStr+ keywordsStr+ trustLvlStr+ "ORDER BY "+orderColumn; st = db.getConnection().prepareStatement(query); int i = 1; if (!allBoards) st.setInt(i++, id); if (keywords != null) { for (int j = 0 ; j < keywords.length ; j++) { String word = keywords[j].toLowerCase(); st.setString(i++, "%"+word+"%"); st.setString(i++, "%"+word+"%"); st.setString(i++, "%"+word+"%"); } } ResultSet set = st.executeQuery(); while(set.next()) { Identity encryptedFor = null; if (set.getInt("encryptedFor") > 0) { encryptedFor = Identity.getIdentity(db, set.getInt("encryptedFor")); } KSKBoard daBoard = ((board != null) ? board : factory.getBoard(set.getInt("boardId"))); if (daBoard == null) Logger.warning(new KSKBoard(), "Can't find the board n°"+Integer.toString(set.getInt("boardId"))+""); int sigId = set.getInt("sigId"); String nick = set.getString("nickname"); v.add(new KSKMessage(set.getInt("id"), set.getString("msgId"), set.getString("inReplyToId"), set.getString("subject"), set.getString("nick"), sigId, (nick != null ? new Identity(db, sigId, nick, set.getString("publicKey"), set.getString("privateKey"), set.getBoolean("isDup"), set.getInt("trustLevel")) : null), set.getTimestamp("date"), set.getInt("rev"), set.getBoolean("read"), set.getBoolean("archived"), encryptedFor, daBoard)); } st.close(); } } catch(SQLException e) { Logger.error(new KSKBoard(), "Can't get message list because : "+e.toString()); } return v; } public Message getNextUnreadMessage(boolean unsigned, boolean archived, int minTrustLevel) { String trustLvlStr; if (unsigned) trustLvlStr = " AND (signatures.trustLevel IS NULL "+ " OR signatures.trustLevel >= "+Integer.toString(minTrustLevel)+") "; else trustLvlStr = " AND signatures.trustLevel >= "+Integer.toString(minTrustLevel)+" "; String archivedStr = ""; if (!archived) archivedStr = " AND frostKSKMessages.archived = FALSE "; try { Hsqldb db = factory.getDb(); synchronized(db.dbLock) { PreparedStatement st; String query = "SELECT frostKSKMessages.id AS id, "+ " frostKSKMessages.msgId AS msgId, "+ " frostKSKMessages.inReplyToId AS inReplyToId, "+ " frostKSKMessages.subject AS subject, "+ " frostKSKMessages.nick AS nick, "+ " frostKSKMessages.sigId AS sigId, "+ " frostKSKMessages.date AS date, "+ " frostKSKMessages.rev AS rev, "+ " frostKSKMessages.encryptedFor AS encryptedFor, "+ " signatures.nickName AS sigNick, "+ " signatures.publicKey AS sigPublicKey, "+ " signatures.privateKey AS sigPrivateKey, "+ " signatures.isDup AS sigIsDup, "+ " signatures.trustLevel AS sigTrustLevel "+ "FROM frostKSKMessages LEFT OUTER JOIN signatures "+ " ON frostKSKMessages.sigId = signatures.id "+ "WHERE frostKSKMessages.boardId = ? "+ "AND frostKSKMessages.read = FALSE "+ archivedStr+ trustLvlStr+ "ORDER BY frostKSKMessages.date LIMIT 1"; st = db.getConnection().prepareStatement(query); st.setInt(1, id); ResultSet set = st.executeQuery(); if (set.next()) { Identity encryptedFor = null; if (set.getInt("encryptedFor") > 0) { encryptedFor = Identity.getIdentity(db, set.getInt("encryptedFor")); } int sigId = set.getInt("sigId"); KSKMessage m = new KSKMessage(set.getInt("id"), set.getString("msgId"), set.getString("inReplyToId"), set.getString("subject"), set.getString("nick"), sigId, (sigId > 0 ? new Identity(db, sigId, set.getString("sigNick"), set.getString("sigPublicKey"), set.getString("sigPrivateKey"), set.getBoolean("sigIsDup"), set.getInt("sigTrustLevel")) : null), set.getTimestamp("date"), set.getInt("rev"), false, false, encryptedFor, this); st.close(); return m; } st.close(); } } catch(SQLException e) { Logger.error(this, "Can't get the next unread message because : "+e.toString()); } return null; } /* last started */ private int lastRev; private Date lastDate; private int lastSuccessfulRev; private int maxDaysInThePast; /* we keep the failed one in this queue as long as no other succeed */ /* sync() on it ! */ private KSKMessage runningDownloads[] = new KSKMessage[MAX_DOWNLOADS_AT_THE_SAME_TIME]; protected Date getCurrentlyRefreshedDate() { return lastDate; } /* for example KSK@frost|message|news|2007.7.21-boards-47.xml */ public final static String KEY_HEADER = /* "KSK@" + */"frost|message|news|"; /** * called by KSKMessage.download(); */ protected String getDownloadKey(Date date, int rev) { java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("yyyy.M.d"); StringBuffer keyBuf = new StringBuffer("KSK@"+KEY_HEADER); keyBuf = formatter.format(date, keyBuf, new java.text.FieldPosition(0)); keyBuf.append("-"+getName()+"-"); keyBuf.append(Integer.toString(rev)); keyBuf.append(".xml"); return keyBuf.toString(); } protected int getKeyType() { return thaw.fcp.FCPClientPut.KEY_TYPE_KSK; } /** * called by KSKDraft */ protected String getPrivateKey() { return null; } /** * called by KSKDraft */ protected String getNameForInsertion(Date date, int rev) { java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("yyyy.M.d"); //formatter.setTimeZone(java.util.TimeZone.getTimeZone("GMT")); StringBuffer keyBuf = new StringBuffer(KEY_HEADER); keyBuf = formatter.format(date, keyBuf, new java.text.FieldPosition(0)); keyBuf.append("-"+getName()+"-"); keyBuf.append(Integer.toString(rev)); keyBuf.append(".xml"); return keyBuf.toString(); } protected static Date getMidnight(Date date) { Calendar cal = new java.util.GregorianCalendar(); cal.setTime(date); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); return cal.getTime(); } protected int getNextNonDownloadedRev(Date daDate, int rev) { daDate = getMidnight(daDate); java.sql.Date date = new java.sql.Date(daDate.getTime()); try { Hsqldb db = factory.getDb(); synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT rev FROM frostKSKMessages "+ "WHERE keyDate = ? "+ "AND rev >= ? AND boardId = ? ORDER by rev"); st.setDate(1, date); st.setInt( 2, rev); st.setInt(3, id); ResultSet set = st.executeQuery(); int lastRev = rev-1; while(set.next()) { int newRev = set.getInt("rev"); if (newRev > lastRev+1) /* there is a hole */ return lastRev+1; lastRev = newRev; } st.close(); /* no hole found */ return lastRev+1; } } catch(SQLException e) { Logger.error(this, "Can't get the next non-downloaded rev in the board because : "+e.toString()); } return -1; } protected int getNextNonDownloadedAndValidRev(Date daDate, int rev) { int nextNonDownloaded; int nextValid = rev+1; do { nextNonDownloaded = getNextNonDownloadedRev(daDate, nextValid); nextValid = getNextValidSlot(daDate, nextNonDownloaded); } while(nextValid != nextNonDownloaded); return nextValid; } protected int getLastDownloadedRev(Date daDate) { daDate = getMidnight(daDate); java.sql.Date date = new java.sql.Date(daDate.getTime()); try { Hsqldb db = factory.getDb(); synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("SELECT rev FROM frostKSKMessages "+ "WHERE keyDate = ? "+ "AND boardId = ? "+ "ORDER by rev DESC "+ "LIMIT 1"); st.setDate(1, date); st.setInt(2, id); ResultSet set = st.executeQuery(); int r = 0; if (set.next()) { r = set.getInt("rev"); } st.close(); return r; } } catch(SQLException e) { Logger.error(this, "Can't get the next non-downloaded rev in the board because : "+e.toString()); } return 0; } /* synchronize your self on runningDownloads */ /** * @param initial true if one of the first downloads of the day */ protected void startNewMessageDownload(boolean initial) { if (!refreshing) return; int slot; /* we search an empty slot */ for (slot = 0 ; slot < MAX_DOWNLOADS_AT_THE_SAME_TIME; slot++) { if (runningDownloads[slot] == null || !runningDownloads[slot].isDownloading()) break; } if (slot == MAX_DOWNLOADS_AT_THE_SAME_TIME) { Logger.notice(this, "No more empty slots ?!"); return; } int rev = getNextNonDownloadedAndValidRev(lastDate, lastRev); Logger.debug(this, "Rev : "+Integer.toString(lastRev)+ " ; "+Integer.toString(rev)+" ; Date : "+lastDate.toString()); runningDownloads[slot] = new KSKMessage(this, lastDate, rev); runningDownloads[slot].addObserver(this); runningDownloads[slot].download(factory.getCore().getQueueManager(), factory.getDb()); if (lastRev < rev) { lastRev = rev; } } protected void notifyChange() { factory.getPlugin().getPanel().notifyChange(this); setChanged(); notifyObservers(); } protected void endOfRefresh() { synchronized(this) { Logger.info(this, "End of refresh"); try { Hsqldb db = factory.getDb(); synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("UPDATE frostKSKBoards "+ "SET lastUpdate = ? "+ "WHERE id = ?"); st.setDate(1, new java.sql.Date(new java.util.Date().getTime())); st.setInt(2, id); st.execute(); st.close(); } } catch(SQLException e) { Logger.error(this, "Unable to update the lastUpdate date :"+e.toString()); } int newMsgs = getNewMessageNumber(); if (newMsgs > 0) { String announce = I18n.getMessage("thaw.plugin.miniFrost.newMsgAnnounce"); announce = announce.replaceAll("X", Integer.toString(newMsgs)); announce = announce.replaceAll("Y", toString()); thaw.plugins.TrayIcon.popMessage(factory.getCore().getPluginManager(), "MiniFrost", announce); } refreshing = false; } notifyChange(); } private Date getNextRefreshDate(Date originalDate) { Date today = getMidnight(new Date()); if (originalDate == null) return today; /* if the last date was in the future */ if (getMidnight(originalDate).getTime() > today.getTime()) { /* TODO : Take into consideration that we could have * MIN_DAYS_IN_THE_FUTURE > 1 */ /* we stop */ return null; } Date newDate = new Date(originalDate.getTime() - 24*60*60*1000); Date maxInPast = new Date(new Date().getTime() - ((maxDaysInThePast+1) * 24*60*60*1000)); Date lastUpdatePast = ((lastUpdate == null) ? null : new Date(lastUpdate.getTime() - (DAYS_BEFORE_THE_LAST_REFRESH * 24*60*60*1000))); if (newDate.getTime() >= maxInPast.getTime() && (lastUpdatePast == null || newDate.getTime() >= lastUpdatePast.getTime())) { /* date in the limits */ return getMidnight(newDate); } else { /* no more in the limits => we do tomorrow and then we stop */ return getMidnight(new Date( (today.getTime()) + 24*60*60*1000)); } } /** * only called when a message has finished its download */ public void update(Observable o, Object param) { synchronized(runningDownloads) { KSKMessage msg = (KSKMessage)o; boolean successful = !msg.isDownloading() && msg.isSuccessful(); if (successful) { //if (msg.isParsable() && !msg.isRead()) // newMsgs++; if (msg.getRev() > lastSuccessfulRev) lastSuccessfulRev = msg.getRev(); int toRestart = 0; /* we restart all the failed ones */ for (int i = 0; i < MAX_DOWNLOADS_AT_THE_SAME_TIME; i++) { if (runningDownloads[i] == null || !runningDownloads[i].isDownloading()) { toRestart++; } } Logger.info(this, "One successful => Restarting "+Integer.toString(toRestart)+" transfers"); for (int i = 0 ; i < toRestart ; i++) startNewMessageDownload(false); } else { /* if not successful, we look if all the other failed */ /* we look first if we can restart some of the failed transfers * up to lastSuccessfulRev + MAX_FAILURES_IN_A_ROW */ boolean moveDay = true; int lastLoadedRev = -1; int nmbFailed = 0; for (int i = 0; i < MAX_DOWNLOADS_AT_THE_SAME_TIME; i++) { if (runningDownloads[i] != null && runningDownloads[i].getRev() > lastLoadedRev) lastLoadedRev = runningDownloads[i].getRev(); } for (int i = 0; i < MAX_DOWNLOADS_AT_THE_SAME_TIME; i++) { if (runningDownloads[i] == null || !runningDownloads[i].isDownloading()) { nmbFailed++; } } if (nmbFailed > MAX_FAILURES_IN_A_ROW) nmbFailed = MAX_FAILURES_IN_A_ROW; Logger.info(this, "One failed"); /* we can't restart more than the number of failed one */ /* and we can't go upper than lastSuccessfulRev + MAX_FAILURES * (hm, in fact, startNewMessageDownload() can go upper ... rah fuck) */ for (int i = 0 ; i < nmbFailed && (lastLoadedRev+1+i) < (lastSuccessfulRev + MAX_FAILURES_IN_A_ROW); i++) { Logger.info(this, "Continuing progression ..."); startNewMessageDownload(false); moveDay = false; } if (!moveDay) return; /* if every transfer has failed, we move to another day */ for (int i = 0 ; i < MAX_DOWNLOADS_AT_THE_SAME_TIME; i++) { if (runningDownloads[i] != null && (runningDownloads[i].isDownloading() || runningDownloads[i].isSuccessful())) { moveDay = false; } } if (moveDay) { Logger.info(this, "no more message to fetch for this day => moving to another"); lastDate = getNextRefreshDate(lastDate); lastRev = -1; if (lastDate != null) { lastSuccessfulRev = getLastDownloadedRev(lastDate); /* we start again */ for (int i = 0; i < MAX_DOWNLOADS_AT_THE_SAME_TIME; i++) { startNewMessageDownload(true); } } else { endOfRefresh(); } } } } /* we notify a change anyway because of KSKDraft */ notifyChange(); } public void refresh() { refresh(MAX_DAYS_IN_THE_PAST); } public void refresh(int maxDaysInThePast) { if (refreshing) { Logger.notice(this, "Already refreshing"); return; } this.maxDaysInThePast = maxDaysInThePast; synchronized(this) { lastDate = getNextRefreshDate(null); lastRev = -1; refreshing = true; } notifyChange(); Thread th = new ThawThread(this, "Board refreshment", this); th.start(); } public void run() { //lastDate = new Date((new Date()).getTime() // + (MIN_DAYS_IN_THE_FUTURE * (24 * 60 * 60 * 1000 /* 1 day */))); synchronized(runningDownloads) { lastSuccessfulRev = getLastDownloadedRev(lastDate); for (int i = 0 ; i < MAX_DOWNLOADS_AT_THE_SAME_TIME ; i++) { runningDownloads[i] = null; } for (int i = 0 ; i < MAX_DOWNLOADS_AT_THE_SAME_TIME ; i++) { startNewMessageDownload(true); } } } public void stop() { /* startNewMessageDownload() won't start any new message download * if this variable is set to false */ refreshing = false; } public boolean isRefreshing() { return refreshing; } protected static int countNewMessages(Hsqldb db, int boardId, String boardName, boolean unsigned, boolean archived, int minTrustLevel) { int count = -1; String archivedStr = ""; if (!archived) archivedStr = " AND frostKSKMessages.archived = FALSE"; String unsignedStr = " AND (frostKSKMessages.sigId IS NULL OR signatures.trustLevel >= ?)"; if (!unsigned) unsignedStr = " AND frostKSKMessages.sigId IS NOT NULL AND signatures.trustLevel >= ?"; String query = "SELECT count(frostKSKMessages.id) "+ "FROM frostKSKMessages LEFT JOIN signatures "+ " ON frostKSKMessages.sigId = signatures.id "+ "WHERE frostKSKMessages.boardId = ? "+ "AND frostKSKMessages.read = FALSE"+ archivedStr+ unsignedStr; try { synchronized(db.dbLock) { PreparedStatement subSt; subSt = db.getConnection().prepareStatement(query); subSt.setInt(1, boardId); subSt.setInt(2, minTrustLevel); ResultSet subRes = subSt.executeQuery(); if (subRes.next()) count = subRes.getInt(1); subSt.close(); } } catch(SQLException e) { Logger.error(db, "Can't count the number of new message on the board "+ "'"+boardName+"'because : "+e.toString()); Logger.error(db, "The query was: "+query); } return count; } boolean lastUnsignedSetting; boolean lastArchivedSetting; int lastMinTrustLevelSetting; /** * just for the announce through the trayicon; */ private int getNewMessageNumber() { return getNewMessageNumber(lastUnsignedSetting, lastArchivedSetting, lastMinTrustLevelSetting); } public int getNewMessageNumber(boolean unsigned, boolean archived, int minTrustLevel) { this.lastUnsignedSetting = unsigned; this.lastArchivedSetting = archived; this.lastMinTrustLevelSetting = minTrustLevel; return countNewMessages(factory.getDb(), id, name, unsigned, archived, minTrustLevel); } public boolean destroy() { refreshing = false; Hsqldb db = factory.getDb(); if (!KSKMessage.destroyAll(this, db)) return false; try { synchronized(db.dbLock) { PreparedStatement st; st = db.getConnection().prepareStatement("DELETE FROM frostKSKInvalidSlots WHERE boardId = ?"); st.setInt(1, id); st.execute(); st.close(); st = db.getConnection().prepareStatement("DELETE FROM frostKSKBoards "+ "WHERE id = ?"); st.setInt(1, id); st.execute(); st.close(); } } catch(SQLException e) { Logger.error(this, "Can't destroy the board because : "+e.toString()); return false; } return true; } public int getId() { return id; } /** * @return the board name, as it */ public String getName() { return name; } /** * Return the board name, * with maybe some informations */ public String toString() { return name; } public KSKBoardFactory getFactory() { return factory; } public Draft getDraft(Message inReplyTo) { return new KSKDraft(this, (KSKMessage)inReplyTo); } public int compareTo(Object o) { return toString().compareToIgnoreCase(o.toString()); } public boolean equals(Object o) { if (!(o instanceof KSKBoard)) return false; return ( ((KSKBoard)o).getId() == getId() ); } public void addInvalidSlot(Date date, int rev) { date = getMidnight(date); Hsqldb db = factory.getDb(); synchronized(db.dbLock) { PreparedStatement st; try { st = db.getConnection().prepareStatement("SELECT id, minRev, maxRev "+ "FROM frostKSKInvalidSlots "+ "WHERE boardId = ? "+ "AND date = ? "+ "AND (maxRev = ? "+ "OR minRev = ?) LIMIT 1"); st.setInt(1, id); st.setDate(2, new java.sql.Date(date.getTime())); st.setInt(3, rev-1); st.setInt(4, rev+1); ResultSet set = st.executeQuery(); if (!set.next()) { /* no existing interval near our rev */ /* => we create one */ st.close(); st = db.getConnection().prepareStatement("INSERT INTO frostKSKInvalidSlots (boardId, date, minRev, maxRev) "+ "VALUES (?, ?, ?, ?)"); st.setInt(1, id); st.setDate(2, new java.sql.Date(date.getTime())); st.setInt(3, rev); st.setInt(4, rev); st.execute(); st.close(); } else { /* an interval near this one already exist */ /* => we adjust */ int intervalId = set.getInt("id"); int intervalMinRev = set.getInt("minRev"); int intervalMaxRev = set.getInt("maxRev"); st.close(); if (intervalMaxRev == (rev-1)) { st = db.getConnection().prepareStatement("UPDATE frostKSKInvalidSlots SET maxRev = ? "+ "WHERE id = ?"); st.setInt(1, rev); st.setInt(2, intervalId); st.execute(); st.close(); } else if (intervalMinRev == (rev+1)) { st = db.getConnection().prepareStatement("UPDATE frostKSKInvalidSlots SET minRev = ? "+ "WHERE id = ?"); st.setInt(1, rev); st.setInt(2, intervalId); st.execute(); st.close(); } else { Logger.error(this, "Unmanaged case !"); } } } catch(SQLException e) { Logger.error(this, "Error while adding invalid slot to the database : "+e.toString()); } } } public int getNextValidSlot(Date date, int rev) { date = getMidnight(date); Hsqldb db = factory.getDb(); synchronized(db.dbLock) { PreparedStatement st; try { st = db.getConnection().prepareStatement("SELECT id, minRev, maxRev "+ "FROM frostKSKInvalidSlots "+ "WHERE boardId = ? "+ "AND date = ? "+ "AND (maxRev >= ? "+ "AND minRev <= ?) LIMIT 1"); while(true) { st.setInt(1, id); st.setDate(2, new java.sql.Date(date.getTime())); st.setInt(3, rev); st.setInt(4, rev); ResultSet set = st.executeQuery(); if (!set.next()) { st.close(); return rev; } rev = set.getInt("maxRev")+1; } } catch(SQLException e) { Logger.error(this, "getNextValidSlot(): "+e.toString()); } } return rev; } }