package freenet.clients.fcp; import java.net.MalformedURLException; import java.util.HashMap; import java.util.Map; import freenet.client.InsertContext; import freenet.client.InsertException; import freenet.client.InsertException.InsertExceptionMode; import freenet.client.async.BaseClientPutter; import freenet.client.async.ClientContext; import freenet.client.async.ClientPutCallback; import freenet.client.events.ClientEvent; import freenet.client.events.ClientEventListener; import freenet.client.events.FinishedCompressionEvent; import freenet.client.events.ExpectedHashesEvent; import freenet.client.events.SplitfileProgressEvent; import freenet.client.events.StartedCompressionEvent; import freenet.clients.fcp.ClientRequest.Persistence; import freenet.keys.FreenetURI; import freenet.keys.InsertableClientSSK; import freenet.node.NodeClientCore; import freenet.support.LogThresholdCallback; import freenet.support.Logger; import freenet.support.Logger.LogLevel; import freenet.support.api.Bucket; /** * Base class for ClientPut and ClientPutDir. * Any code which can be shared between the two goes here. */ public abstract class ClientPutBase extends ClientRequest implements ClientPutCallback, ClientEventListener { private static final long serialVersionUID = 1L; /** Created new for each ClientPutBase, so we have to delete it in requestWasRemoved() */ final InsertContext ctx; // Verbosity bitmasks private static final int VERBOSITY_SPLITFILE_PROGRESS = 1; private static final int VERBOSITY_EXPECTED_HASHES = 8; // same as ClientGet private static final int VERBOSITY_PUT_FETCHABLE = 256; private static final int VERBOSITY_COMPRESSION_START_END = 512; // Stuff waiting for reconnection /** Has the request succeeded? */ protected boolean succeeded; /** If the request failed, how did it fail? PutFailedMessage is the most * convenient way to store this (InsertException has a stack trace!). */ protected PutFailedMessage putFailedMessage; /** URI generated for the insert. */ protected FreenetURI generatedURI; // This could be a SimpleProgress, or it could be started/finished compression. // Not that important, so not saved on persistence. // Probably saving it would conflict with later changes (full persistence at // ClientPutter level). protected transient FCPMessage progressMessage; protected final FreenetURI publicURI; /** Metadata returned instead of URI */ private Bucket generatedMetadata; public final static String SALT = "Salt"; public final static String FILE_HASH = "FileHash"; private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback(){ @Override public void shouldUpdate(){ logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } }); } private static Map<Integer, UploadFrom> uploadFromByCode = new HashMap<Integer, UploadFrom>(); public enum UploadFrom { // Codes must be constant at least for migration DIRECT(0), DISK(1), REDIRECT(2); final int code; UploadFrom(int code) { if(uploadFromByCode.containsKey(code)) throw new Error("Duplicate"); uploadFromByCode.put(code, this); this.code = code; } public static UploadFrom getByCode(int x) { UploadFrom u = uploadFromByCode.get(x); if(u == null) throw new IllegalArgumentException(); return u; } } public ClientPutBase(FreenetURI uri, String identifier, int verbosity, String charset, FCPConnectionHandler handler, short priorityClass, Persistence persistence, String clientToken, boolean global, boolean getCHKOnly, boolean dontCompress, boolean localRequestOnly, int maxRetries, boolean earlyEncode, boolean canWriteClientCache, boolean forkOnCacheable, String compressorDescriptor, int extraInsertsSingleBlock, int extraInsertsSplitfileHeader, boolean realTimeFlag, InsertContext.CompatibilityMode compatibilityMode, boolean ignoreUSKDatehints, FCPServer server) throws MalformedURLException { super(uri, identifier, verbosity, charset, handler, priorityClass, persistence, realTimeFlag, clientToken, global); ctx = server.core.clientContext.getDefaultPersistentInsertContext(); ctx.getCHKOnly = getCHKOnly; ctx.dontCompress = dontCompress; ctx.eventProducer.addEventListener(this); ctx.maxInsertRetries = maxRetries; ctx.canWriteClientCache = canWriteClientCache; ctx.compressorDescriptor = compressorDescriptor; ctx.forkOnCacheable = forkOnCacheable; ctx.extraInsertsSingleBlock = extraInsertsSingleBlock; ctx.extraInsertsSplitfileHeaderBlock = extraInsertsSplitfileHeader; ctx.setCompatibilityMode(compatibilityMode); ctx.localRequestOnly = localRequestOnly; ctx.earlyEncode = earlyEncode; ctx.ignoreUSKDatehints = ignoreUSKDatehints; publicURI = this.uri.deriveRequestURIFromInsertURI(); } protected ClientPutBase() { // For serialization. ctx = null; publicURI = null; } static FreenetURI checkEmptySSK(FreenetURI uri, String filename, ClientContext context) { if("SSK".equals(uri.getKeyType()) && uri.getDocName() == null && uri.getRoutingKey() == null) { if(filename == null || filename.equals("")) filename = "key"; // SSK@ = use a random SSK. InsertableClientSSK key = InsertableClientSSK.createRandom(context.random, ""); return key.getInsertURI().setDocName(filename); } else { return uri; } } public ClientPutBase(FreenetURI uri, String identifier, int verbosity, String charset, FCPConnectionHandler handler, PersistentRequestClient client, short priorityClass, Persistence persistence, String clientToken, boolean global, boolean getCHKOnly, boolean dontCompress, int maxRetries, boolean earlyEncode, boolean canWriteClientCache, boolean forkOnCacheable, boolean localRequestOnly, int extraInsertsSingleBlock, int extraInsertsSplitfileHeader, boolean realTimeFlag, String compressorDescriptor, InsertContext.CompatibilityMode compatMode, boolean ignoreUSKDatehints, NodeClientCore core) throws MalformedURLException { super(uri, identifier, verbosity, charset, handler, client, priorityClass, persistence, realTimeFlag, clientToken, global); ctx = core.clientContext.getDefaultPersistentInsertContext(); ctx.getCHKOnly = getCHKOnly; ctx.dontCompress = dontCompress; ctx.eventProducer.addEventListener(this); ctx.maxInsertRetries = maxRetries; ctx.canWriteClientCache = canWriteClientCache; ctx.compressorDescriptor = compressorDescriptor; ctx.forkOnCacheable = forkOnCacheable; ctx.extraInsertsSingleBlock = extraInsertsSingleBlock; ctx.extraInsertsSplitfileHeaderBlock = extraInsertsSplitfileHeader; ctx.localRequestOnly = localRequestOnly; ctx.setCompatibilityMode(compatMode); ctx.ignoreUSKDatehints = ignoreUSKDatehints; ctx.earlyEncode = earlyEncode; publicURI = this.uri.deriveRequestURIFromInsertURI(); } @Override public void onLostConnection(ClientContext context) { if(persistence == Persistence.CONNECTION) cancel(context); // otherwise ignore } @Override public void onSuccess(BaseClientPutter state) { synchronized(this) { // Including this helps with certain bugs... //progressMessage = null; started = true; // FIXME remove, used by resuming succeeded = true; finished = true; completionTime = System.currentTimeMillis(); if(generatedURI == null) Logger.error(this, "No generated URI in onSuccess() for "+this+" from "+state); } if (persistence == Persistence.CONNECTION) { freeData(); } finish(); trySendFinalMessage(null, null); if(client != null) client.notifySuccess(this); } @Override public void onFailure(InsertException e, BaseClientPutter state) { if(finished) return; synchronized(this) { started = true; // FIXME remove, used by resuming finished = true; completionTime = System.currentTimeMillis(); putFailedMessage = new PutFailedMessage(e, identifier, global); } if (persistence == Persistence.CONNECTION) { freeData(); } finish(); trySendFinalMessage(null, null); if(client != null) client.notifyFailure(this); } @Override public void onGeneratedURI(FreenetURI uri, BaseClientPutter state) { synchronized(this) { if(generatedURI != null) { if(!uri.equals(generatedURI)) Logger.error(this, "onGeneratedURI("+uri+ ',' +state+") but already set generatedURI to "+generatedURI); else if(logMINOR) Logger.minor(this, "onGeneratedURI() twice with same value: "+generatedURI+" -> "+uri); } else { generatedURI = uri; } } trySendGeneratedURIMessage(null, null); if(client != null) { RequestStatusCache cache = client.getRequestStatusCache(); if(cache != null) { cache.gotFinalURI(identifier, uri); } } } public FreenetURI getGeneratedURI() { return generatedURI; } @Override public void onGeneratedMetadata(Bucket metadata, BaseClientPutter state) { boolean delete = false; synchronized(this) { if(generatedURI != null) Logger.error(this, "Got generated metadata but already have URI on "+this+" from "+state); if(generatedMetadata != null) { Logger.error(this, "Already got generated metadata from "+state+" on "+this); delete = true; } else { generatedMetadata = metadata; } } if(delete) { metadata.free(); } else { trySendGeneratedMetadataMessage(metadata, null, null); } } @Override public void requestWasRemoved(ClientContext context) { // if request is still running, send a PutFailed with code=cancelled if( !finished ) { synchronized(this) { finished = true; InsertException cancelled = new InsertException(InsertExceptionMode.CANCELLED); putFailedMessage = new PutFailedMessage(cancelled, identifier, global); } trySendFinalMessage(null, null); } // notify client that request was removed FCPMessage msg = new PersistentRequestRemovedMessage(getIdentifier(), global); if(persistence == Persistence.CONNECTION) origHandler.outputHandler.queue(msg); else client.queueClientRequestMessage(msg, 0); freeData(); Bucket meta; synchronized(this) { meta = generatedMetadata; generatedMetadata = null; } // FIXME combine the synchronized blocks, null out even if non-persistent. if(meta != null) { meta.free(); } if(persistence == Persistence.FOREVER) { synchronized(this) { putFailedMessage = null; generatedURI = null; progressMessage = null; } } super.requestWasRemoved(context); } @Override public void receive(final ClientEvent ce, ClientContext context) { if(finished) return; if(logMINOR) Logger.minor(this, "Receiving event "+ce+" on "+this); if(ce instanceof SplitfileProgressEvent) { if((verbosity & VERBOSITY_SPLITFILE_PROGRESS) == VERBOSITY_SPLITFILE_PROGRESS) { SimpleProgressMessage progress = new SimpleProgressMessage(identifier, global, (SplitfileProgressEvent)ce); trySendProgressMessage(progress, VERBOSITY_SPLITFILE_PROGRESS, null, context); } if(client != null) { RequestStatusCache cache = client.getRequestStatusCache(); if(cache != null) { cache.updateStatus(identifier, (SplitfileProgressEvent)ce); } } } else if(ce instanceof StartedCompressionEvent) { if((verbosity & VERBOSITY_COMPRESSION_START_END) == VERBOSITY_COMPRESSION_START_END) { StartedCompressionMessage msg = new StartedCompressionMessage(identifier, global, ((StartedCompressionEvent)ce).codec); trySendProgressMessage(msg, VERBOSITY_COMPRESSION_START_END, null, context); onStartCompressing(); } } else if(ce instanceof FinishedCompressionEvent) { if((verbosity & VERBOSITY_COMPRESSION_START_END) == VERBOSITY_COMPRESSION_START_END) { FinishedCompressionMessage msg = new FinishedCompressionMessage(identifier, global, (FinishedCompressionEvent)ce); trySendProgressMessage(msg, VERBOSITY_COMPRESSION_START_END, null, context); onStopCompressing(); } } else if(ce instanceof ExpectedHashesEvent) { if((verbosity & VERBOSITY_EXPECTED_HASHES) == VERBOSITY_EXPECTED_HASHES) { ExpectedHashes msg = new ExpectedHashes((ExpectedHashesEvent)ce, identifier, global); trySendProgressMessage(msg, VERBOSITY_EXPECTED_HASHES, null, context); //FIXME: onHashesComputed(); } } } protected abstract void onStopCompressing(); protected abstract void onStartCompressing(); @Override public void onFetchable(BaseClientPutter putter) { if(finished) return; if((verbosity & VERBOSITY_PUT_FETCHABLE) == VERBOSITY_PUT_FETCHABLE) { FreenetURI temp; synchronized (this) { temp = generatedURI; } PutFetchableMessage msg = new PutFetchableMessage(identifier, global, temp); trySendProgressMessage(msg, VERBOSITY_PUT_FETCHABLE, null, null); } } private void trySendFinalMessage(FCPConnectionOutputHandler handler, String listRequestIdentifier) { FCPMessage msg; synchronized (this) { if(succeeded) { msg = new PutSuccessfulMessage(identifier, global, generatedURI, startupTime, completionTime); } else { msg = putFailedMessage; } } if(msg == null) { Logger.error(this, "Trying to send null message on "+this, new Exception("error")); } else { if(persistence == Persistence.CONNECTION && handler == null) handler = origHandler.outputHandler; if(handler != null) handler.queue(FCPMessage.withListRequestIdentifier(msg, listRequestIdentifier)); else client.queueClientRequestMessage(FCPMessage.withListRequestIdentifier(msg, listRequestIdentifier), 0); } } private void trySendGeneratedURIMessage(FCPConnectionOutputHandler handler, String listRequestIdentifier) { FCPMessage msg; synchronized(this) { msg = new URIGeneratedMessage(generatedURI, identifier, isGlobalQueue()); } if(persistence == Persistence.CONNECTION && handler == null) handler = origHandler.outputHandler; if(handler != null) handler.queue(FCPMessage.withListRequestIdentifier(msg, listRequestIdentifier)); else client.queueClientRequestMessage(msg, 0); } /** * @param metadata Activated by caller. * @param handler * @param listRequestIdentifier */ private void trySendGeneratedMetadataMessage(Bucket metadata, FCPConnectionOutputHandler handler, String listRequestIdentifier) { FCPMessage msg = FCPMessage.withListRequestIdentifier(new GeneratedMetadataMessage(identifier, global, metadata), listRequestIdentifier); if(persistence == Persistence.CONNECTION && handler == null) handler = origHandler.outputHandler; if(handler != null) handler.queue(msg); else client.queueClientRequestMessage(msg, 0); } /** * @param msg * @param verbosity * @param handler * @param container Either container or context is required for a persistent request. * @param context Can be null if container is not null. */ private void trySendProgressMessage(final FCPMessage msg, final int verbosity, FCPConnectionOutputHandler handler, ClientContext context) { synchronized(this) { if(persistence != Persistence.CONNECTION) progressMessage = msg; } if(persistence == Persistence.CONNECTION && handler == null) handler = origHandler.outputHandler; if(handler != null) handler.queue(msg); else client.queueClientRequestMessage(msg, verbosity); } protected abstract FCPMessage persistentTagMessage(); @Override public void sendPendingMessages(FCPConnectionOutputHandler handler, String listRequestIdentifier, boolean includePersistentRequest, boolean includeData, boolean onlyData) { if(includePersistentRequest) { FCPMessage msg = FCPMessage.withListRequestIdentifier(persistentTagMessage(), listRequestIdentifier); handler.queue(msg); } boolean generated = false; FCPMessage msg = null; boolean fin = false; Bucket meta; synchronized (this) { generated = generatedURI != null; msg = FCPMessage.withListRequestIdentifier(progressMessage, listRequestIdentifier); fin = finished; meta = generatedMetadata; } if(generated) trySendGeneratedURIMessage(handler, listRequestIdentifier); if(meta != null) trySendGeneratedMetadataMessage(meta, handler, listRequestIdentifier); if(msg != null) handler.queue(msg); if(fin) trySendFinalMessage(handler, listRequestIdentifier); } protected abstract String getTypeName(); @Override public synchronized double getSuccessFraction() { if(progressMessage != null) { if(progressMessage instanceof SimpleProgressMessage) return ((SimpleProgressMessage)progressMessage).getFraction(); else return 0; } else return -1; } @Override public synchronized double getTotalBlocks() { if(progressMessage != null) { if(progressMessage instanceof SimpleProgressMessage) return ((SimpleProgressMessage)progressMessage).getTotalBlocks(); else return 0; } else return -1; } @Override public synchronized double getMinBlocks() { if(progressMessage != null) { if(progressMessage instanceof SimpleProgressMessage) return ((SimpleProgressMessage)progressMessage).getMinBlocks(); else return 0; } else return -1; } @Override public synchronized double getFailedBlocks() { if(progressMessage != null) { if(progressMessage instanceof SimpleProgressMessage) return ((SimpleProgressMessage)progressMessage).getFailedBlocks(); else return 0; } else return -1; } @Override public synchronized double getFatalyFailedBlocks() { if(progressMessage != null) { if(progressMessage instanceof SimpleProgressMessage) return ((SimpleProgressMessage)progressMessage).getFatalyFailedBlocks(); else return 0; } else return -1; } @Override public synchronized double getFetchedBlocks() { if(progressMessage != null) { if(progressMessage instanceof SimpleProgressMessage) return ((SimpleProgressMessage)progressMessage).getFetchedBlocks(); else return 0; } else return -1; } @Override public synchronized boolean isTotalFinalized() { if(!(progressMessage instanceof SimpleProgressMessage)) return false; else { return ((SimpleProgressMessage)progressMessage).isTotalFinalized(); } } @Override public synchronized String getFailureReason(boolean longDescription) { if(putFailedMessage == null) return null; String s = putFailedMessage.shortCodeDescription; if(longDescription && putFailedMessage.extraDescription != null) s += ": "+putFailedMessage.extraDescription; return s; } public PutFailedMessage getFailureMessage() { if(putFailedMessage == null) return null; return putFailedMessage; } public synchronized void setVarsRestart() { finished = false; this.putFailedMessage = null; this.progressMessage = null; started = false; } }