package plugins.CENO.Bridge; import java.io.IOException; import java.util.Date; import java.util.Hashtable; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; import plugins.CENO.Common.URLtoUSKTools; import freenet.client.InsertException; import freenet.client.async.BaseClientPutter; import freenet.client.async.ClientContext; import freenet.client.async.ClientPutCallback; import freenet.keys.FreenetURI; import freenet.node.RequestClient; import freenet.support.Logger; import freenet.support.api.Bucket; import freenet.support.io.ResumeFailedException; public class BundleInserter { private static final BundleInserter INSTANCE = new BundleInserter(); private static final long SHOULD_REINSERT = TimeUnit.HOURS.toMillis(5); private final int MAX_CONCURRENT_INSERTIONS = 10; private volatile Integer concurrentInsertions = 0; private Hashtable<String, Date> insertTable = new Hashtable<String, Date>(); private Queue<InsertionStruct> insertionsQueue = new ConcurrentLinkedQueue<InsertionStruct>(); private BundleInserter() {} public static synchronized BundleInserter getInstance() { return INSTANCE; } private void addToQueue(InsertionStruct insertionStruct) { synchronized (concurrentInsertions) { if (concurrentInsertions <= MAX_CONCURRENT_INSERTIONS) { // If Bundle Inserter has not reached the maximum concurrent insertions, // then insert the bundle immediately rather than offering it to the queue doInsert(insertionStruct); } else { insertionsQueue.add(insertionStruct); } } } private void processQueue() { synchronized (concurrentInsertions) { while (concurrentInsertions <= MAX_CONCURRENT_INSERTIONS && !insertionsQueue.isEmpty()) { doInsert(insertionsQueue.poll()); } } } public boolean shouldInsert(String url) { if (!insertTable.containsKey(url) || new Date().getTime() - insertTable.get(url).getTime() > SHOULD_REINSERT) { return true; } else { return false; } } public void updateTableEntry(String url) { insertTable.put(url, new Date(new Date().getTime())); } private synchronized void doInsert(InsertionStruct insertionStruct) { if (shouldInsert(insertionStruct.url)) { try { CENOBridge.nodeInterface.insertBundleManifest(insertionStruct.insertURI, insertionStruct.content, insertionStruct.docName, insertionStruct.insertCb); } catch (IOException e) { Logger.error(this, "Failed to initiate insertion for a bundle, IO Error: " + e.getMessage()); Logger.normal(this, "Failed to initiate insertion for URL: " + insertionStruct.url + " IO Error: " + e.getMessage()); return; } catch (InsertException e) { Logger.error(this, "Failed to initiate insertion for a bundle, Insert Error: " + e.getMessage()); Logger.normal(this, "Failed to initiate insertion for URL: " + insertionStruct.url + " Insert Error: " + e.getMessage()); addToQueue(insertionStruct); return; } concurrentInsertions++; updateTableEntry(insertionStruct.url); } } public void insertBundle(String url) throws IOException, InsertException { InsertCallback insertCb = new InsertCallback(url); insertBundle(url, insertCb); } public void insertBundle(String url, ClientPutCallback insertCallback) throws IOException, InsertException { Bundle bundle = new Bundle(url); bundle.requestFromBundler(); insertBundle(url, bundle, insertCallback); } public void insertBundle(String url, Bundle bundle) throws IOException, InsertException { insertBundle(url, bundle, new InsertCallback(url)); } public void insertBundle(String url, Bundle bundle, ClientPutCallback insertCallback) throws IOException, InsertException { if (bundle.getContent().isEmpty()) { throw new IOException("Bundle content for url " + url + " was empty, will not insert"); } FreenetURI insertKey = URLtoUSKTools.computeInsertURI(url, CENOBridge.initConfig.getProperty("insertURI")); Logger.normal(BundleInserter.class, "Initiating bundle insertion for URL: " + url); insertBundleManifest(url, insertKey, null, bundle.getContent(), insertCallback); } private void insertBundleManifest(String url, FreenetURI insertURI, String docName, String content, ClientPutCallback insertCallback) throws IOException, InsertException { addToQueue(new InsertionStruct(content, url, docName, insertCallback, insertURI)); } private class InsertionStruct { String content, url, docName; ClientPutCallback insertCb; FreenetURI insertURI; public InsertionStruct(String content, String url, String docName, ClientPutCallback insertCallback, FreenetURI insertURI) { this.content = content; this.url = url; this.docName = docName; this.insertCb = insertCallback; this.insertURI = insertURI; } } public class InsertCallback implements ClientPutCallback { protected FreenetURI cachedURI; protected String url; public InsertCallback(String url) { this.url = url; } public void onGeneratedURI(FreenetURI freenetUri, BaseClientPutter state) { this.cachedURI = freenetUri; } public void onGeneratedMetadata(Bucket metadata, BaseClientPutter state) { } public void onFetchable(BaseClientPutter state) { } public void onSuccess(BaseClientPutter state) { Logger.normal(this, "Bundle caching for URL " + url + " successful: " + cachedURI); synchronized (concurrentInsertions) { concurrentInsertions--; processQueue(); } } public void onFailure(InsertException e, BaseClientPutter state) { Logger.error(this, "Failed to insert bundle for a URL, Error Message: " + e); Logger.normal(this, "Failed to insert bundle for URL " + url + " Error Message: " + e); synchronized (concurrentInsertions) { concurrentInsertions--; processQueue(); } } public void onResume(ClientContext context) throws ResumeFailedException { } public RequestClient getRequestClient() { return CENOBridge.nodeInterface.getRequestClient(); } } }