package uc.files.transfer;
import helpers.GH;
import helpers.PreferenceChangedAdapter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import logger.LoggerFactory;
import org.apache.log4j.Logger;
import uc.DCClient;
import uc.PI;
import uc.protocols.Compression.FinishWrapper;
import uc.protocols.client.ClientProtocol;
public class Upload extends AbstractFileTransfer {
private static final int UPDATES_PER_SECOND = 10;
private static final int DRAINTIME = UPDATES_PER_SECOND*5;
private static Semaphore globalUploads = new Semaphore(1);
// private static final CopyOnWriteArrayList<Semaphore> RUNNING_UPLOADS = new CopyOnWriteArrayList<Semaphore>();
// private final Semaphore localUploads = new Semaphore(1);
private volatile static ScheduledFuture<?> updateTask;
private static void update() {
if (updateTask != null) {
updateTask.cancel(false);
}
updateTask = DCClient.getScheduler().scheduleAtFixedRate(new Runnable() {
private final int maxSpeed = PI.getInt(PI.uploadLimit) <= 0?
Integer.MAX_VALUE / DRAINTIME
: PI.getInt(PI.uploadLimit);
private int i = 0;
private int overDue = 0;
public void run() {
// int divFactor= RUNNING_UPLOADS.size() * UPDATES_PER_SECOND;
// if (++i % DRAINTIME == 0 ) {
// int drained = 0;
// for (Semaphore sem:RUNNING_UPLOADS) {
// drained += sem.drainPermits();
// }
// overDue += drained % divFactor;
// }
if (++i % DRAINTIME == 0 ) {
globalUploads.drainPermits();
}
int releaseGlobal = maxSpeed+overDue;
globalUploads.release(releaseGlobal / UPDATES_PER_SECOND);
overDue = releaseGlobal % UPDATES_PER_SECOND;
// int releaseLocal = (maxSpeed+overDue);
// if (releaseLocal > 0) {
// for (Semaphore sem:RUNNING_UPLOADS) {
// sem.release(releaseLocal);
// }
// }
// overDue = releaseLocal % divFactor;
}
},1000/UPDATES_PER_SECOND,1000/UPDATES_PER_SECOND,TimeUnit.MILLISECONDS);
}
static {
new PreferenceChangedAdapter(PI.get(),PI.uploadLimit) {
@Override
public void preferenceChanged(String preference,String oldValue, String newValue) {
update();
}
};
update();
}
private static final Logger logger = LoggerFactory.make();
private volatile long bytesTransferred;
private AbstractReadableFileInterval fileInterval;
private volatile WritableByteChannel target;
private volatile ByteChannel sink; //channel representing the internet connection
public Upload(FileTransferInformation fti,ClientProtocol cp, AbstractReadableFileInterval rfi) throws IOException {
super(fti,cp);
fileInterval = rfi;
}
public void cancel() {
GH.close(sink,target); //closes sink first -> should prevent closing target from blocking
}
@Override
public boolean isUpload() {
return true;
}
public AbstractFileInterval getFileInterval() {
return fileInterval;
}
@Override
public void transferData(ByteChannel sink) throws IOException {
this.sink = sink;
FinishWrapper fw = getCompression().wrapOutgoing(sink); //add compression
this.compressor = fw;
this.target = fw.getChan();
ReadableByteChannel source = fileInterval.getReadableChannel();
ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE);
bb.clear();
// RUNNING_UPLOADS.add(localUploads);
int written;
int toAcquire = 0;
try {
notifyObservers( TransferChange.STARTED);
// logger.info("Upload started: "+fileInterval.toString());
while ( source.read(bb) >= 0 || bb.position() != 0) {
bb.flip();
if ((written = target.write(bb)) < 0) {
break;
}
bytesTransferred += written;
toAcquire += written;
globalUploads.acquireUninterruptibly(toAcquire/1024);
//localUploads.acquireUninterruptibly(toAcquire/1024);
toAcquire %= 1024 ;
bb.compact();
}
} finally {
// RUNNING_UPLOADS.remove(localUploads);
GH.close(source);
notifyObservers(TransferChange.FINISHED);
if (bytesTransferred == fileInterval.length) { //only on success wrap up
fw.finnish();
}
// logger.info("Upload "+(bytesTransferred==fileInterval.length?"succ":"fail")
// +": " +fileInterval.toString()+" bytes transferred: "+bytesTransferred);
}
logger.debug("finished upload");
}
@Override
public long getBytesTransferred() {
return bytesTransferred;
}
}