package thaw.fcp; import java.util.Hashtable; import java.util.Iterator; import java.util.Vector; import thaw.core.Logger; import thaw.core.ThawThread; import thaw.core.ThawRunnable; /** * Manage a running and a pending queue of FCPTransferQuery. * Please notice that runningQueue contains too finished queries. * Notify when: a query is added and when a query change to one queue to another. */ public class FCPQueueManager extends java.util.Observable implements ThawRunnable, java.util.Observer { private final static int PRIORITY_MIN = 6; /* So 0 to 6 */ private FCPQueryManager queryManager; private int maxDownloads, maxInsertions; /* offset in the array == priority */ /* Vector contains FCPQuery */ private final Vector[] pendingQueries = new Vector[FCPQueueManager.PRIORITY_MIN+1]; private Vector runningQueries; private Hashtable keyTable; private Hashtable filenameTable; private Thread scheduler; private boolean stopThread = false; private int lastId; private String thawId; private boolean queueCompleted; /** * Calls setQueryManager() and then resetQueues(). */ public FCPQueueManager(final FCPQueryManager queryManager, final String thawId, final int maxDownloads, final int maxInsertions) { lastId = 0; queueCompleted = false; setThawId(thawId); setMaxDownloads(maxDownloads); setMaxInsertions(maxInsertions); setQueryManager(queryManager); resetQueues(); queryManager.getConnection().addObserver(this); } public boolean isQueueCompletlyLoaded() { return queueCompleted; } public void setQueueCompleted() { queueCompleted = true; } /** * Use it if you want to bypass the queue. */ public FCPQueryManager getQueryManager() { return queryManager; } public void setThawId(final String thawId) { this.thawId = thawId; } public void setMaxDownloads(final int maxDownloads) { this.maxDownloads = maxDownloads; } public void setMaxInsertions(final int maxInsertions) { this.maxInsertions = maxInsertions; } /** * You should call resetQueues() after calling this function. */ public void setQueryManager(final FCPQueryManager queryManager) { this.queryManager = queryManager; } /** * Will purge the current known queue. */ public void resetQueues() { runningQueries = new Vector(); for(int i = 0; i <= FCPQueueManager.PRIORITY_MIN ; i++) pendingQueries[i] = new Vector(); keyTable = new Hashtable(); filenameTable = new Hashtable(); } /** * Take care: Can change while you're using it. */ public Vector[] getPendingQueues() { return pendingQueries; } /** * Take care: Can change while you're using it. * The running queue contains running request, but also finished/failed ones. * synchronize on it if you want to do iterate() on it. */ public Vector getRunningQueue() { return runningQueries; } /** * @return < 0 if no limit */ public int getMaxDownloads() { return maxDownloads; } /** * @return < 0 if no limit */ public int getMaxInsertions() { return maxInsertions; } /** * @return false if already added. */ public boolean addQueryToThePendingQueue(final FCPTransferQuery query) { if(query.getThawPriority() < 0) return this.addQueryToTheRunningQueue(query); if(isAlreadyPresent(query)) { Logger.notice(this, "Key was already in one of the queues : "+query.getFilename()); return false; } Logger.notice(this, "Adding query to the pending queue ..."); synchronized(pendingQueries) { pendingQueries[query.getThawPriority()].add(query); } String fileKey = query.getFileKey(); String filename = query.getFilename(); if (FreenetURIHelper.isAKey(fileKey)) keyTable.put(FreenetURIHelper.getComparablePart(fileKey), query); filenameTable.put(filename, query); setChanged(); this.notifyObservers(query); Logger.notice(this, "Adding done"); return true; } /** * will call start() function of the query. * @return false if already added */ public boolean addQueryToTheRunningQueue(final FCPTransferQuery query) { return this.addQueryToTheRunningQueue(query, true); } public boolean addQueryToTheRunningQueue(final FCPTransferQuery query, boolean callStart) { Logger.debug(this, "Adding query to the running queue ..."); if(isAlreadyPresent(query)) { Logger.notice(this, "Key was already in one of the queues"); return false; } if(!callStart && query.getIdentifier() != null && query.getIdentifier().startsWith(thawId)) { /* It's a resumed query => We to adapt the next Id * to avoid collisions. */ /* FIXME (not urgent) : Find a cleaner / safer way. */ try { String[] subId = query.getIdentifier().split("-"); subId = subId[0].split("_"); final int id = Integer.parseInt(subId[subId.length-1]); if(id > lastId) { lastId = id; } } catch(final Exception e) { Logger.notice(this, "Exception while parsing previous Id (Not really a problem)"); } } if(callStart) query.start(this); synchronized(runningQueries) { runningQueries.add(query); } String fileKey = query.getFileKey(); String filename = query.getFilename(); if (FreenetURIHelper.isAKey(fileKey)) keyTable.put(FreenetURIHelper.getComparablePart(fileKey), query); if (filename != null) filenameTable.put(filename, query); setChanged(); this.notifyObservers(query); Logger.debug(this, "Adding done"); return true; } /** * *Doesn't* call stop() from the query. */ public void moveFromRunningToPendingQueue(final FCPTransferQuery query) { remove(query); addQueryToThePendingQueue(query); } /** * Restart non-persistent and non-finished queries being in the runninQueue. * Usefull to restart these query when thaw just start. */ public void restartNonPersistent() { Logger.info(this, "Restarting non persistent query"); for(final Iterator queryIt = getRunningQueue().iterator() ; queryIt.hasNext();) { final FCPTransferQuery query = (FCPTransferQuery)queryIt.next(); if(!query.isPersistent() && !query.isFinished()) query.start(this); } Logger.info(this, "Restart done."); } /** * Don't stop() */ public void remove(final FCPTransferQuery query) { synchronized(runningQueries) { runningQueries.remove(query); } synchronized(pendingQueries) { for(int i = 0 ; i <= FCPQueueManager.PRIORITY_MIN ; i++) pendingQueries[i].remove(query); } String fileKey = query.getFileKey(); String filename = query.getFilename(); if (FreenetURIHelper.isAKey(fileKey)) keyTable.remove(FreenetURIHelper.getComparablePart(fileKey)); filenameTable.remove(filename); setChanged(); this.notifyObservers(query); } private boolean isTheSame(final FCPTransferQuery queryA, final FCPTransferQuery queryB) { if(queryA.getQueryType() != queryB.getQueryType()) return false; if((queryA.getIdentifier() != null) && (queryB.getIdentifier() != null)) { if(queryA.getIdentifier().equals(queryB.getIdentifier())) { Logger.debug(this, "isTheSame(): Identifier"); return true; } return false; } if((queryA.getFileKey() != null) && (queryB.getFileKey() != null)) { if(queryA.getFileKey().equals(queryB.getFileKey())) { Logger.debug(this, "isTheSame(): FileKey"); return true; } return false; } if(queryA.getFilename() != null && queryA.getFilename().equals(queryB.getFilename())) { Logger.debug(this, "isTheSame(): Filename"); return true; } return false; } /** * Compare only the refs. */ public boolean isInTheQueues(final FCPTransferQuery query) { synchronized(runningQueries) { if(runningQueries.contains(query)) return true; } synchronized(pendingQueries) { for(int i = 0 ; i < pendingQueries.length ; i++) { if(pendingQueries[i].contains(query)) return true; } } return false; } /** * @param key file key or file name if key is unknown */ public FCPTransferQuery getTransfer(final String key) { FCPTransferQuery q; if (FreenetURIHelper.isAKey(key)) { q = (FCPTransferQuery)keyTable.get(FreenetURIHelper.getComparablePart(key)); if (q != null) return q; return (FCPTransferQuery)filenameTable.get(FreenetURIHelper.getFilenameFromKey(key)); } return (FCPTransferQuery)filenameTable.get(key); } /** * Compare using the key. */ public boolean isAlreadyPresent(final FCPTransferQuery query) { Iterator it; synchronized(runningQueries) { for(it = runningQueries.iterator(); it.hasNext(); ) { final FCPTransferQuery plop = (FCPTransferQuery)it.next(); if(isTheSame(plop, query)) return true; } } synchronized(pendingQueries) { for(int i = 0 ; i <= FCPQueueManager.PRIORITY_MIN ; i++) { for(it = pendingQueries[i].iterator(); it.hasNext(); ) { final FCPTransferQuery plop = (FCPTransferQuery)it.next(); if(isTheSame(plop, query)) return true; } } } return false; } private void schedule() { /* We count the running query to see if there is an empty slot */ int runningInsertions = 0; int runningDownloads = 0; synchronized(runningQueries) { for(final Iterator it = runningQueries.iterator(); it.hasNext(); ) { final FCPTransferQuery query = (FCPTransferQuery)it.next(); if((query.getQueryType() == 1 /* Download */) && !query.isFinished()) runningDownloads++; if((query.getQueryType() == 2 /* Insertion */) && !query.isFinished()) runningInsertions++; } } /* We move queries from the pendingQueue to the runningQueue until we got our quota */ for(int priority = 0; (priority <= FCPQueueManager.PRIORITY_MIN) && ( ((maxInsertions <= -1) || (runningInsertions < maxInsertions)) || ((maxDownloads <= -1) || (runningDownloads < maxDownloads)) ) ; priority++) { synchronized(pendingQueries) { for(Iterator it = pendingQueries[priority].iterator(); it.hasNext() && ( ((maxInsertions <= -1) || (runningInsertions < maxInsertions)) || ((maxDownloads <= -1) || (runningDownloads < maxDownloads)) ); ) { final FCPTransferQuery query = (FCPTransferQuery)it.next(); if( ((query.getQueryType() == 1) && ((maxDownloads <= -1) || (runningDownloads < maxDownloads)) ) || ((query.getQueryType() == 2) && ((maxInsertions <= -1) || (runningInsertions < maxInsertions))) ) { Logger.debug(this, "Scheduler : Moving a query from pendingQueue to the runningQueue"); pendingQueries[priority].remove(query); it = pendingQueries[priority].iterator(); /* We reset iterator */ this.addQueryToTheRunningQueue(query); if(query.getQueryType() == 1) runningDownloads++; if(query.getQueryType() == 2) runningInsertions++; try { Thread.sleep(300); } catch(final java.lang.InterruptedException e) { } } } } } } private void updateStats() { synchronized(runningQueries) { for (Iterator it = runningQueries.iterator(); it.hasNext(); ) { FCPTransferQuery query = (FCPTransferQuery)it.next(); query.updateStats(); } } } public void run() { try { Thread.sleep(5000); } catch(final java.lang.InterruptedException e) { /* \_o< */ } while(!stopThread) { try { Thread.sleep(1000); } catch(final java.lang.InterruptedException e) { /* We don't care */ } if(!stopThread) { try { if(queryManager.getConnection().isConnected() && queueCompleted) schedule(); if(queryManager.getConnection().isConnected()) updateStats(); } catch(final Exception e) { Logger.error(this, "EXCEPTION FROM FCP SCHEDULER : "+e.toString()+ " ; "+e.getMessage()); e.printStackTrace(); } } } } public void stop() { stopThread = true; } public void startScheduler() { scheduler = new ThawThread(this, "FCP queue scheduler", this); stopThread = false; scheduler.start(); } public void stopScheduler() { stopThread = true; } public String getAnID() { lastId++; if(lastId >= 2147483647 /* 2^31 - 1 */) { lastId = 0; } return (thawId+"_"+ Integer.toString(lastId)); } public boolean isOur(String queryId) { return queryId.startsWith(thawId); } public void update(final java.util.Observable o, final Object arg) { if((o == queryManager.getConnection()) && !queryManager.getConnection().isConnected()) { /* Only the running queue ... * pending queries are specifics to Thaw */ runningQueries = new Vector(); setChanged(); notifyObservers(); } } }