package thaw.fcp;
import java.util.HashMap;
import java.util.Observable;
import thaw.core.Logger;
/**
* Transfer query == fetch / insert query. These queries must be able to
* give more informations than the other.
* Functions returning status of the request may be call frequently, so try to make them fast.
* Some methods are only useful for downloads, and some for insertions, so check getQueryType() before calling them.
*/
public abstract class FCPTransferQuery extends Observable implements FCPQuery {
public final static int BLOCK_SIZE = 32768;
public final static int KEY_TYPE_CHK = 0;
public final static int KEY_TYPE_KSK = 1;
public final static int KEY_TYPE_SSK = 2; /* also USK */
public final static int DEFAULT_PRIORITY = 4;
public final static int DEFAULT_MAX_RETRIES = -1;
public final static int PERSISTENCE_FOREVER = 0;
public final static int PERSISTENCE_UNTIL_NODE_REBOOT = 1;
public final static int PERSISTENCE_UNTIL_DISCONNECT = 2;
private boolean insertion = false;
/* last known values */
private long requiredBlocks = -1;
private long totalBlocks = -1;
private long transferedBlocks = -1;
private boolean reliable = false;
/* reminder to do the maths */
public final static int NMB_REMINDERS = 300; /* one per seconde, so 5 minutes here */
private long[] transferedBlocksPast = new long[NMB_REMINDERS];
private int currentReadCursor = 0; /* read Cursor in the *past arrays */
private int currentWriteCursor = 0; /* write Cursor in the *past arrays */
private boolean running = false;
private boolean finished = false;
private boolean successful = false;
private long averageSpeed = 0;
private long ETA = 0;
private long startupTime = -1;
private long completionTime = -1;
private String id;
/**
* @param id can be null if currently unknown
* @param insertion
*/
protected FCPTransferQuery(String id, boolean insertion) {
setIdentifier(id);
this.insertion = insertion;
reliable = insertion;
for (int i = 0 ; i < NMB_REMINDERS ; i++) {
transferedBlocksPast[i] = -1;
}
}
protected void setIdentifier(String id) {
if (id == null || "".equals(id.trim()))
this.id = null;
else
this.id = id.trim();
}
public String getIdentifier() {
return id;
}
protected void setBlockNumbers(long required, long total, long transfered, boolean reliable) {
synchronized(this) {
requiredBlocks = required;
totalBlocks = total;
transferedBlocks = transfered;
this.reliable = reliable;
}
}
protected void setStatus(boolean running, boolean finished, boolean successful) {
this.running = running;
this.finished = ((!running) ? finished : false);
this.successful = (finished ? successful : false);
}
/**
* Called about each second by the queueManager. Used to make the average speed and the ETA
* as precise as possible
*/
protected void updateStats()
{
synchronized(this) {
/* computing average speed & ETA */
if (completionTime >= 0 && startupTime >= 0 && finished)
{ /* wooch, final values ! :) */
long blocks = (insertion) ? totalBlocks : requiredBlocks;
long diffTime = (completionTime - startupTime) / 1000;
if (blocks <= 0 || diffTime <= 0)
return;
long prevAverageSpeed = averageSpeed;
long prevETA = ETA;
averageSpeed = (blocks * BLOCK_SIZE) / diffTime;
ETA = diffTime; /* ok, it's a little bit icky, but it does the trick :) */
if (prevAverageSpeed != averageSpeed || prevETA != ETA)
notifyChange(new Long(ETA));
return;
}
if (!running || finished)
return;
if (transferedBlocks < 0)
return;
if ((reliable || insertion) && (currentReadCursor != currentWriteCursor)) {
if (transferedBlocksPast[currentReadCursor] < 0)
Logger.warning(this, "TransferedBlocksNumber < 0, shouldn't happen !");
/* reminder : we have one second between each slot of the *Past arrays */
long diffTimeSec = ((currentWriteCursor < currentReadCursor) ? currentWriteCursor+NMB_REMINDERS : currentWriteCursor) - currentReadCursor;
long diffBlocks = transferedBlocks - transferedBlocksPast[currentReadCursor];
long remainingBlocks = (insertion ? (totalBlocks - transferedBlocks) : (requiredBlocks - transferedBlocks));
//Logger.notice(this, "T: "+Long.toString(diffTimeSec)+ " ; B: "+ Long.toString(diffBlocks)+" ; R: "+Long.toString(remainingBlocks));
if (diffTimeSec <= 0 || diffBlocks <= 0 || remainingBlocks == 0) {
if (diffBlocks < 0)
Logger.warning(this, "DiffBlocks < 0, shouldn't happen !");
if (diffTimeSec < 0)
Logger.warning(this, "DiffTimeSec < 0, shouldn't happen !");
averageSpeed = 0;
ETA = 0;
} else {
//averageSpeed = (diffBlocks*BLOCK_SIZE) / diffTimeSec;
double averageSpeedInBlocksPerSecond = ((double)diffBlocks) / diffTimeSec;
averageSpeed = (long)(((double)averageSpeedInBlocksPerSecond) * ((double)BLOCK_SIZE));
if (averageSpeed >= 0.00000001) {
ETA = (long)((double)remainingBlocks / averageSpeedInBlocksPerSecond);
/* Logger.notice(this, "R: "+Long.toString(remainingBlocks)
* + " ; AS: "+Double.toString(averageSpeedInBlocksPerSecond)
* + " ; ETA: "+Long.toString(ETA));
*/
} else {
/*Logger.notice(this, "R: "+Long.toString(remainingBlocks)
* + " ; AS: "+Double.toString(averageSpeedInBlocksPerSecond)
* + " ; ETA == 0");
*/
ETA = 0;
}
}
if (currentWriteCursor == currentReadCursor-1
|| (currentWriteCursor == NMB_REMINDERS-1 && currentReadCursor == 0)) {
/* the currentWriteCursor will push the currentReadCursor */
currentReadCursor++;
if (currentReadCursor >= NMB_REMINDERS)
currentReadCursor = 0;
}
notifyChange();
}
/* updating known values */
transferedBlocksPast[currentWriteCursor] = transferedBlocks;
currentWriteCursor++;
if (currentWriteCursor >= NMB_REMINDERS)
currentWriteCursor = 0;
}
}
/**
* @return in bytes / s (0 = unknown)
*/
public long getAverageSpeed()
{
return averageSpeed;
}
/**
* @return in second
*/
public long getETA()
{
return ETA;
}
/**
* When did the request start ?
*/
public long getStartupTime()
{
return startupTime;
}
protected void setStartupTime(long startupTime)
{
this.startupTime = startupTime;
}
/**
* When did the request finish ?
* @return -1 if not finished
*/
public long getCompletionTime()
{
return completionTime;
}
protected void setCompletionTime(long time)
{
this.completionTime = time;
}
/**
* Informal.
* Is about the transfer on the network.
* In pourcents.
*/
public int getProgression()
{
if (finished)
return 100;
if (transferedBlocks < 0)
return 0;
if (insertion) {
if (totalBlocks <= 0) return 0;
return (int)(transferedBlocks * 100 / totalBlocks);
} else {
if (requiredBlocks <= 0) return 0;
return (int)(transferedBlocks * 99 / requiredBlocks);
}
}
public boolean isProgressionReliable()
{
if (getProgression() == 0)
return true;
return reliable || insertion;
}
public boolean isRunning()
{
return running;
}
public boolean isFinished()
{
return finished;
}
/**
* If unknow, return false.
* Query is considered as a failure is isFinished() && !isSuccesful()
*/
public boolean isSuccessful()
{
return successful;
}
public void notifyChange() {
setChanged();
notifyObservers();
}
public void notifyChange(Object o) {
setChanged();
notifyObservers(o);
}
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof FCPTransferQuery)) return false;
if (getIdentifier() == null) return false;
if (((FCPTransferQuery)o).getIdentifier() == null) return false;
if (((FCPTransferQuery)o).getIdentifier() == getIdentifier()) return true;
return getIdentifier().equals(((FCPTransferQuery)o).getIdentifier());
}
/**** To implement to implement a FCPTransferQuery: ****/
/**
* Stop the transfer, but don't consider it as failed.
* @param queueManager QueueManager gives access to QueryManager;
*/
public abstract boolean pause(FCPQueueManager queueManager);
/**
* Should only be called if isFinished() == true && isSuccessful() == false
*/
public abstract boolean isFatallyFailed();
/**
* Only if persistent. Remove it from the queue.
*/
public abstract boolean removeRequest();
/**
* Used by the QueueManager only.
* Currently these priority are the same
* as FCP priority, but it can change in the
* future.
* -1 = No priority
* Always between -1 and 6.
*/
public abstract int getThawPriority();
/**
* Currently the same than Thaw priority.
*/
public abstract int getFCPPriority();
/**
* call updatePersistentRequest() after to apply the change (Please note that the change
* will be visible even if you don't call it).
*/
public abstract void setFCPPriority(int prio);
/**
* you can call it after saveFileTo() to update the clientToken.
* @param clientToken tell if the clientToken must be updated or just the priority
*/
public abstract void updatePersistentRequest(boolean clientToken);
/**
* Informal.
* Human readable string describring the
* status of the query.
* @return can be null (== "Waiting")
*/
public abstract String getStatus();
/**
* For persistent request only.
* @param dir Directory
*/
public abstract boolean saveFileTo(String dir);
/**
* Is about the transfer between the node and thaw.
*/
public abstract int getTransferWithTheNodeProgression();
/**
* Informal.
* Gives *public* final key only.
* @return can be null
*/
public abstract String getFileKey();
/**
* Informal. In bytes.
* @return can be -1
*/
public abstract long getFileSize();
/**
* Where is the file on the disk.
*/
public abstract String getPath();
/**
* @return can return -1
*/
public abstract int getAttempt();
public abstract void setAttempt(int x);
/**
* @return can return -1
*/
public abstract int getMaxAttempt();
/**
* Use to save the query in an XML file / a database / whatever.
* @return A HashMap : String (parameter name) -> String (parameter value) or null.
*/
public abstract HashMap getParameters();
/**
* Opposite of getParameters().
* @return true if successful (or ignored) ; false if not.
*/
public abstract boolean setParameters(HashMap parameters);
public abstract boolean isGlobal();
public abstract boolean isPersistent();
public abstract String getFilename();
}