/**
* Copyright (C) 2013 CLXY Studio.
* This content is released under the (Link Goes Here) MIT License.
* http://en.wikipedia.org/wiki/MIT_License
*/
package cn.clxy.upload;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class UploadFileService {
private File file;
private BlockingQueue<Part> parts;
private List<Integer> indexes;
private Listener listener = new Listener.Default();
private Uploader uploader = new ApacheHCUploader();
private ExecutorService executor = Executors.newFixedThreadPool(Config.maxUpload);
private static final Log log = LogFactory.getLog(UploadFileService.class);
public UploadFileService(String fileName) {
file = new File(fileName);
if (!file.exists() || !file.isFile()) {
throw new RuntimeException("File:" + file + " isn't correct!");
}
}
public void upload() {
try {
doUpload();
} finally {
stop();
}
}
public void retry(Integer... array) {
// sort first.
indexes = Arrays.asList(array);
Collections.sort(indexes);
try {
doUpload();
} finally {
stop();
}
}
public void stop() {
if (executor != null) {
executor.shutdown();
}
}
private void doUpload() {
listener.onStart(indexes != null ? indexes.size() : getPartCount());
parts = new ArrayBlockingQueue<Part>(Config.maxRead);
CompletionService<String> cs = new ExecutorCompletionService<String>(executor);
cs.submit(readTask);
for (int i = 0; i < Config.maxUpload; i++) {
cs.submit(new UploadTask("upload." + i));
}
// Wait all done. total count = maxUpload + 1.
for (int i = 0; i <= Config.maxUpload; i++) {
Future<String> future = null;
try {
future = cs.take();
checkFuture(future);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// Notify sever all done.
Future<String> result = executor.submit(notifyTask);
checkFuture(result);
listener.onSuccess();
}
private String checkFuture(Future<String> future) {
String result = null;
try {
result = future.get();
return result;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
} catch (ExecutionException e) {
listener.onFail(result);
log.error(e.getCause());
throw new RuntimeException(e.getCause());
}
}
protected int getPartCount() {
long length = file.length();
long count = (length / Config.partSize) + (length % Config.partSize == 0 ? 0 : 1);
return (int) count;
}
private Callable<String> readTask = new Callable<String>() {
@Override
public String call() throws Exception {
FileInputStream fis = null;
String fileName = file.getName();
int partSize = Config.partSize;
try {
fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
for (int i = 0;; i++) {
ReadStatus status = getReadStatus(i, indexes);
if (status == ReadStatus.stop) {
break;
}
if (status == ReadStatus.skip) {
fc.position(fc.position() + partSize);
continue;
}
if (status == ReadStatus.read) {
ByteBuffer bb = ByteBuffer.allocate(partSize);
int bytesRead = fc.read(bb);
if (bytesRead == -1) {
break;
}
byte[] bytes = bb.array();
if (bytesRead != partSize) {// trim
bytes = Arrays.copyOf(bytes, bytesRead);
}
String partName = createFileName(fileName, i);
listener.onRead(partName);
parts.put(new Part(partName, bytes));
}
}
} finally {
if (fis != null) {
try {
fis.close();
} catch (Exception e) {
}
}
parts.put(Part.NULL);// put end signal.
}
return "read";
}
/**
* Create file name of part. <br>
* bigfile.avi = [bigfile.avi<strong>.0</strong>, bigfile.avi.1, bigfile.avi.2 ...]
* @param fileName
* @param i
* @return
*/
protected String createFileName(String fileName, int i) {
return fileName + "." + i;// start by 0.
// return fileName + (i == 0 ? "" : ("." + i));
}
private ReadStatus getReadStatus(int i, List<Integer> indexes) {
if (indexes == null || indexes.contains(i)) {
return ReadStatus.read;
}
if (i > indexes.get(indexes.size() - 1)) {
return ReadStatus.stop;
}
return ReadStatus.skip;
}
};
private static enum ReadStatus {
stop, skip, read
}
private class UploadTask implements Callable<String> {
private String name;
public UploadTask(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
while (true) {
Part part = parts.take();
if (part == Part.NULL) {
parts.add(Part.NULL);// notify others to stop.
break;
}
String partName = part.getName();
listener.onUpload(partName);
uploader.upload(part);
listener.onPartDone(partName);
}
return name;
}
}
private Callable<String> notifyTask = new Callable<String>() {
@Override
public String call() throws Exception {
uploader.done(file.getName(), getPartCount());
return "notify";
}
};
}