/** * */ package uc.files.transfer; import helpers.IObservable; import helpers.Observable; import helpers.StatusObject; import helpers.StatusObject.ChangeType; import java.io.IOException; import java.nio.channels.ByteChannel; import java.util.Date; import java.util.LinkedList; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import logger.LoggerFactory; import org.apache.log4j.Logger; import uc.ConnectionHandler; import uc.DCClient; import uc.IUser; import uc.protocols.Compression; import uc.protocols.ConnectionState; import uc.protocols.Compression.ICompIO; import uc.protocols.client.ClientProtocol; /** * * Replacement for the old FileTransfer.. * as it lacked to many things.. * * @author Quicksilver * */ public abstract class AbstractFileTransfer extends Observable<TransferChange> implements IFileTransfer { private static final Logger logger = LoggerFactory.make(); protected static final int BUFFER_SIZE = 4*1024; private static CopyOnWriteArrayList<AbstractFileTransfer> active = new CopyOnWriteArrayList<AbstractFileTransfer>(); /** * retrieves the total speed of all running transfers.. * @param upload - * @return */ public static long getTotalSpeed(boolean upload) { long totalSpeed = 0; // int counted = 0; for (AbstractFileTransfer c: active) { if (c.isUpload() == upload) { totalSpeed += c.getSpeed(); // counted++; } } //logger.info("counted: "+counted+" "+active.size()); // return totalSpeed; } private final FileTransferInformation fti; private volatile Date starttime; /* protected CopyOnWriteArrayList<ITransferListener> transferListeners = new CopyOnWriteArrayList<ITransferListener>(); */ private volatile boolean finished = false; /** * estimates the current speed.. */ private volatile long currentSpeed; private static final float alpha = 0.9f ; private volatile long currentSmoothedSpeed; private volatile float currentCompressionRatio = 1.0f; protected ICompIO compressor; protected final ClientProtocol cp; protected AbstractFileTransfer(FileTransferInformation fti,ClientProtocol cp) throws IOException { this.cp = cp; this.fti = fti; if (!fti.isValid()) { throw new IOException("could not create a transfer from a non valid transfer info"); } addObserver(new ServiceListener(cp.getDcc(),cp.getCh())); // registerTransferListener(); } /** * transfers the data to or from the provided internet connection * @param internetConnection - ususally a socket to the other peer. * @throws IOException */ public abstract void transferData(ByteChannel internetConnection) throws IOException ; public String getNameOfTransferred() { return fti.getNameOfTransferred(); } public IUser getOther() { return fti.getOther(); } public long getSpeed() { return currentSmoothedSpeed; //currentSpeed; } public Date getStartTime() { if (starttime != null) { return new Date(starttime.getTime()); } else { return new Date(); //say now as it will start soon.. } } public boolean hasStarted() { return starttime != null; } public abstract long getBytesTransferred(); public Compression getCompression() { return fti.getCompression(); } public FileTransferInformation getFti() { return fti; } /* * * @return compression ration on the transfers.. */ public float getCompressionRatio() { return currentCompressionRatio; } /* * updates compression ratio to new value.. */ protected void updateCompressionRatio() { long currentComp = compressor.getCompIO(); long current = compressor.getIO(); currentCompressionRatio = (float)currentComp/current; } public long getTimeRemaining() { long speed = currentSpeed; if (speed == 0 || currentSmoothedSpeed == 0 ) { if (finished) { return 0; // zero seconds for finished transfers } else { return Long.MAX_VALUE; } } long remaining = getFileInterval().length() - getFileInterval().getRelativeCurrentPos(); return remaining / currentSmoothedSpeed; } public boolean isUpload() { return fti.isUpload(); } public boolean isDownload() { return fti.isDownload(); } /** * client protocol responsible for this transfer. */ public ClientProtocol getClientProtocol() { return cp; } /** * used by the abstractFileTransfer itself * to set values like the * time remaining or the startTime or * the speed.. * * easier for speed would be using * current = a*oldvalue + (1-a) * newValue changing alpha to to set adaption to changes.. * may be interesting for a longterm speed assumption... * * @author Quicksilver * */ class ServiceListener implements IObserver<TransferChange> { private static final int TIMESSPEEDMAYBEZERO = 120; //means speed may be 0 for one minute private int timesSpeedIsZero = 0; private final Runnable update = new Runnable() { public void run() { notifyObservers(TransferChange.BYTESTRANSFERRED); } }; private ScheduledFuture<?> sf; private final DCClient dcc; private final ConnectionHandler ch; public ServiceListener(DCClient dcc,ConnectionHandler ch){ this.dcc = dcc; this.ch = ch; } /** * list for measuring average speed over the last seconds.. */ private final LinkedList<TimePositionTuple> measured = new LinkedList<TimePositionTuple>(); public synchronized void update(IObservable<TransferChange> fileTransfer, TransferChange change) { switch(change) { case STARTED: // logger.info("started has fired.."+getOther()+" "+AbstractFileTransfer.this.cp.getState()); sf = dcc.getSchedulerDir().scheduleAtFixedRate(update, 500, 500, TimeUnit.MILLISECONDS); starttime = new Date(); active.addIfAbsent(AbstractFileTransfer.this); ch.notifyTransferObservers(new StatusObject(AbstractFileTransfer.this, ChangeType.ADDED)); break; case BYTESTRANSFERRED: TimePositionTuple current = new TimePositionTuple(getFileInterval().getCurrentPosition()); measured.add(current); while (measured.size() > 30) { measured.removeFirst(); } TimePositionTuple old = measured.getFirst(); currentSpeed = old.calcSpeed(current); currentSmoothedSpeed =(long)(currentSmoothedSpeed * alpha + currentSpeed * (1-alpha)); if (currentSpeed == 0) { timesSpeedIsZero++; if (timesSpeedIsZero > TIMESSPEEDMAYBEZERO) { timesSpeedIsZero = 0; if (AbstractFileTransfer.this.cp.getState() == ConnectionState.DESTROYED) { logger.debug("Speed zero "+getOther()+" "+AbstractFileTransfer.this.cp.getState()); update(AbstractFileTransfer.this,TransferChange.FINISHED); logger.debug("unnormal finished called "+getOther()); } else { cancel(); } } } else { timesSpeedIsZero = 0; } updateCompressionRatio(); ch.notifyTransferObservers(new StatusObject(AbstractFileTransfer.this, ChangeType.CHANGED)); break; case FINISHED: logger.debug("finished has fired: "+getOther()+" "+AbstractFileTransfer.this.cp.getState()+" "+timesSpeedIsZero); fti.getOther().deleteConnection(AbstractFileTransfer.this.cp); sf.cancel(false); currentSpeed = 0; finished = true; active.remove(AbstractFileTransfer.this); ch.notifyTransferObservers(new StatusObject(AbstractFileTransfer.this, ChangeType.REMOVED)); break; } } } private static class TimePositionTuple { private final long position; private final long date; public TimePositionTuple(long position) { this.position = position; this.date = System.currentTimeMillis(); } public long calcSpeed(TimePositionTuple current) { if (date < current.date) { return (current.position - position)*1000/(current.date-date); } else { return 0; } } } }