/* * Copyright 2014 Loic Merckel * Copyright 2014 Dirk Boye * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * * The original version of this file (i.e., the one that is copyrighted 2014 Dirk Boye) * can be found here: * * https://github.com/dirkboye/GDriveUpload * * Massive changes have been made * */ package io.uploader.drive.drive.largefile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.api.client.repackaged.com.google.common.base.Preconditions; import io.uploader.drive.config.HasConfiguration; import io.uploader.drive.drive.DriveUtils.HasDescription; import io.uploader.drive.drive.DriveUtils.HasId; import io.uploader.drive.drive.DriveUtils.HasMimeType; import io.uploader.drive.util.FileUtils.InputStreamProgressFilter; import java.io.*; import java.net.URISyntaxException; import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; class GDriveUpload { private static final Logger logger = LoggerFactory.getLogger(GDriveUpload.class); private final HasConfiguration config ; private final String title ; private final String filename ; private final InputStreamProgressFilter.StreamProgressCallback progressCallback ; private final HasMimeType mimeType ; private final HasId parentId ; private final HasDescription description ; private final HasId fileId ; private final String tmpFilePath ; private String md5 = null ; // https://developers.google.com/google-apps/documents-list/#uploading_a_new_document_or_file_with_both_metadata_and_content // Important: Always choose a chunk size that is a multiple of 512 kilobytes. The last chunk may be smaller than 512 kilobytes. private final int chunkSize = 10 * 1024 * 1024 ; protected GDriveUpload (HasConfiguration config, String title, HasDescription description, HasId parentId, HasMimeType mimeType, String filename, InputStreamProgressFilter.StreamProgressCallback progressCallback) { super () ; this.config = config ; this.title = title ; this.filename = filename ; this.progressCallback = progressCallback ; this.mimeType = mimeType ; this.parentId = parentId ; this.description = description ; this.fileId = null ; tmpFilePath = config.getTmpDirectory() + title + ".tmp" ; logger.info ("Tmp file: " + tmpFilePath) ; } protected GDriveUpload (HasConfiguration config, HasId fileId, HasMimeType mimeType, String filename, InputStreamProgressFilter.StreamProgressCallback progressCallback) { super () ; this.config = config ; this.title = null ; this.filename = filename ; this.progressCallback = progressCallback ; this.mimeType = mimeType ; this.parentId = null ; this.description = null ; this.fileId = fileId ; tmpFilePath = config.getTmpDirectory() + Paths.get(filename).getFileName().toString() + "-update.tmp" ; logger.info ("Tmp file: " + tmpFilePath) ; } private static class TransferException extends IOException { private static final long serialVersionUID = 1L; private final boolean notResumable ; public TransferException(boolean notResumable, String message) { super(message); this.notResumable = notResumable ; } public boolean isNotResumable() { return notResumable; } } private String uploadFile(DriveResumableUpload upload, BasicFileAttributes attr) throws IOException { long currentBytePosition = upload.getCurrentByte(); File file = new File(filename); if (currentBytePosition > -1 && currentBytePosition < attr.size()) { byte[] chunk; int retries = 0; while (retries < 5) { InputStream stream = io.uploader.drive.util.FileUtils .getInputStreamWithProgressFilter( progressCallback, attr.size(), new FileInputStream(file)) ; if (currentBytePosition > 0) { stream.skip(currentBytePosition); } chunk = new byte[chunkSize]; int bytes_read = stream.read(chunk, 0, chunkSize); stream.close(); if (bytes_read > 0) { int status = upload.uploadChunk(chunk, currentBytePosition, bytes_read); if (status == 308) { // If Status is 308 RESUME INCOMPLETE there's no retry done. retries = 0; } else if (status >= 500 && status < 600) { // Good practice: Exponential backoff try { long seconds = Math.round(Math.pow(2, retries + 1)); logger.info("Exponential backoff. Waiting " + seconds + " seconds."); Thread.sleep(seconds*1000); } catch(InterruptedException ex) { Thread.currentThread().interrupt(); } } else if (status == 401) { logger.info("Tokan has experied, need to be refreshed...") ; upload.updateAccessToken(); } else if (status == 200 || status == 201) { boolean success = upload.checkMD5(md5); logger.info("local md5sum: " + md5); logger.info("File upload complete."); if (!success) { throw new TransferException (false, "The md5 values do not macth") ; } break ; } else if (status == 404) { // this can be due to a remaining temporary file with an out-dated link // we throw that exception with no recovery option (not resumable) in order to // delete this file (if any) throw new TransferException (false, "The file cannot be found") ; } else { logger.info("Status: " + String.valueOf(status)); } } ++retries; currentBytePosition = upload.getCurrentByte(); } } else if (currentBytePosition == attr.size ()) { boolean success = upload.checkMD5(md5); logger.info("local md5sum: " + md5); logger.info("File upload complete."); if (!success) { throw new IOException ("The md5 values do not macth") ; } } else { // Some BUG occured. lastbyte = -1. throw new TransferException (false, "Some anomalies have been observed") ; } // get file id return upload.getFileId() ; } protected String uploadFile(boolean update) throws IOException { Preconditions.checkState(update == (fileId != null)); DriveResumableUpload upload = null; BasicFileAttributes attr = null; File fstatus = null; try { String googleLocation; String filestatus = tmpFilePath; attr = io.uploader.drive.util.FileUtils.getFileAttr(Paths .get(filename)); fstatus = new File(filestatus); if (fstatus.exists()) { BufferedReader in = new BufferedReader(new FileReader( filestatus)); md5 = in.readLine(); googleLocation = in.readLine(); in.close(); if (update) { upload = new DriveResumableUpload( config.getHttpProxySettings(), new DriveAuth(config), googleLocation, fileId, mimeType, filename, attr.size(), progressCallback); } else { upload = new DriveResumableUpload( config.getHttpProxySettings(), new DriveAuth(config), googleLocation, title, description, parentId, mimeType, filename, attr.size(), progressCallback); } } else { if (md5 == null) { FileInputStream fis = new FileInputStream( new File(filename)); md5 = org.apache.commons.codec.digest.DigestUtils .md5Hex(fis); logger.info("md5: " + md5); fis.close(); } // Completely new upload. location: null if (update) { upload = new DriveResumableUpload( config.getHttpProxySettings(), new DriveAuth(config), null, fileId, mimeType, filename, attr.size(), progressCallback); } else { upload = new DriveResumableUpload( config.getHttpProxySettings(), new DriveAuth(config), null, title, description, parentId, mimeType, filename, attr.size(), progressCallback); } // Write location and md5 to file for later resume BufferedWriter out = new BufferedWriter(new FileWriter( filestatus)); out.write(md5 + "\n" + upload.getLocation() + "\n"); out.close(); } } catch (URISyntaxException e) { logger.error("Error occurred while uploading files", e); throw new RuntimeException("Error occurred while uploading files " + e.getMessage()); } try { String ret = uploadFile(upload, attr); if (fstatus != null && fstatus.exists()) { fstatus.delete(); } return ret; } catch (TransferException e) { if (e.isNotResumable()) { if (fstatus != null && fstatus.exists()) { fstatus.delete(); } } throw new IOException (e.getMessage()) ; } } }