package de.tuberlin.onedrivesdk.uploadFile; import com.google.gson.Gson; import de.tuberlin.onedrivesdk.OneDriveException; import de.tuberlin.onedrivesdk.common.ConcreteOneDriveSDK; import de.tuberlin.onedrivesdk.file.ConcreteOneFile; import de.tuberlin.onedrivesdk.file.OneFile; import de.tuberlin.onedrivesdk.folder.ConcreteOneFolder; import de.tuberlin.onedrivesdk.networking.OneDriveAuthenticationException; import de.tuberlin.onedrivesdk.networking.OneResponse; import de.tuberlin.onedrivesdk.networking.PreparedRequest; import de.tuberlin.onedrivesdk.networking.PreparedRequestMethod; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.concurrent.locks.ReentrantLock; import static com.google.common.base.Preconditions.checkNotNull; /** * Implementation of OneUploadFile, blocking operation */ public class ConcreteOneUploadFile implements OneUploadFile { private static final int chunkSize = 320 * 1024 * 30; // (use a multiple value of 320KB, best practice of dev.onedrive) private static final Logger logger = LogManager.getLogger(ConcreteOneUploadFile.class); private static final Gson gson = new Gson(); private final ReentrantLock shouldRun = new ReentrantLock(true); private File fileToUpload; private ConcreteOneDriveSDK api; private boolean canceled = false; private boolean finished = false; private UploadSession uploadSession; private RandomAccessFile randFile; private String uploadUrl =""; public ConcreteOneUploadFile(ConcreteOneFolder parentFolder, File fileToUpload, ConcreteOneDriveSDK api) throws IOException, OneDriveAuthenticationException { checkNotNull(parentFolder); this.api = checkNotNull(api); if (fileToUpload != null) { if (fileToUpload.isFile()) { if (fileToUpload.canRead()) { this.fileToUpload = fileToUpload; randFile = new RandomAccessFile(fileToUpload, "r"); } else { throw new IOException(String.format("File %s is not readable!", fileToUpload.getName())); } } else { throw new IOException(String.format("%s is not a File", fileToUpload.getAbsolutePath())); } } else { throw new NullPointerException("FileToUpload was null"); } this.uploadSession = api.createUploadSession(parentFolder, fileToUpload.getName()); this.uploadUrl = this.uploadSession.getUploadURL(); } @Override public long fileSize() { return fileToUpload.length(); } @Override public long uploadStatus() throws IOException, OneDriveException { if (uploadSession != null) { PreparedRequest request = new PreparedRequest(this.uploadUrl, PreparedRequestMethod.GET); OneResponse response = api.makeRequest(request); if (response.wasSuccess()) { return gson.fromJson(response.getBodyAsString(), UploadSession.class).getNextRange(); } else { throw new OneDriveException(response.getBodyAsString()); } } return 0; } @Override public OneFile startUpload() throws IOException, OneDriveException { byte[] bytes; ConcreteOneFile finishedFile = null; OneResponse response; while (!canceled && !finished) { shouldRun.lock(); long currFirstByte = randFile.getFilePointer(); PreparedRequest uploadChunk = new PreparedRequest(this.uploadUrl, PreparedRequestMethod.PUT); if (currFirstByte + chunkSize < randFile.length()) { bytes = new byte[chunkSize]; } else { // optimistic cast, assuming the last bit of the file is // never bigger than MAXINT bytes = new byte[(int) (randFile.length() - randFile.getFilePointer())]; } long start = randFile.getFilePointer(); randFile.readFully(bytes); uploadChunk.setBody(bytes); uploadChunk.addHeader("Content-Length", (randFile.getFilePointer() - start) + ""); uploadChunk.addHeader( "Content-Range", String.format("bytes %s-%s/%s", start, randFile.getFilePointer() - 1, randFile.length())); logger.trace("Uploading chunk {} - {}", start, randFile.getFilePointer() - 1); response = api.makeRequest(uploadChunk); if (response.wasSuccess()) { if (response.getStatusCode()==200 || response.getStatusCode()==201) { // if last chunk upload was successful end the finished = true; finishedFile = gson.fromJson(response.getBodyAsString(), ConcreteOneFile.class); }else { //just continue uploadSession = gson.fromJson(response.getBodyAsString(), UploadSession.class); randFile.seek(uploadSession.getNextRange()); } } else { logger.info("Something went wrong while uploading last chunk. Trying to fetch upload status from server to retry"); logger.trace(response.getBodyAsString()); response = api.makeRequest(this.uploadUrl, PreparedRequestMethod.GET, null); if (response.wasSuccess()) { uploadSession = gson.fromJson( response.getBodyAsString(), UploadSession.class); randFile.seek(uploadSession.getNextRange()); logger.debug("Fetched updated uploadSession. Server requests {} as next chunk",uploadSession.getNextRange()); } else { canceled=true; logger.info("Something went wrong while uploading. Was unable to fetch the currentUpload session from the Server"); throw new OneDriveException( String.format("Could not get current upload status from Server, aborting. Message was: %s", response.getBodyAsString())); } } shouldRun.unlock(); } logger.info("finished upload"); finishedFile.setApi(api); return finishedFile; } @Override public OneUploadFile pauseUpload() { logger.info("Pausing upload"); shouldRun.lock(); logger.info("Upload paused"); return this; } @Override public OneUploadFile resumeUpload() { logger.info("Resuming upload"); try{ shouldRun.unlock(); logger.info("Upload resumed"); }catch (IllegalMonitorStateException e) { logger.info("Trying to resume an already running download"); } return this; } @Override public OneUploadFile cancelUpload() throws IOException, OneDriveAuthenticationException { logger.info("Canceling upload"); this.canceled = true; if (uploadSession != null) { api.makeRequest(this.uploadUrl, PreparedRequestMethod.DELETE, ""); logger.info("Upload was canceled"); } return this; } @Override public File getUploadFile() { return this.fileToUpload; } @Override public OneFile call() throws IOException, OneDriveException { logger.info("Starting upload"); return startUpload(); } }