/* * Copyright (c) 2014 Loic Merckel * Copyright (c) 2013 Google Inc. * * 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 2013 Google Inc.) * can be found here: * * http://code.google.com/p/google-api-java-client/source/checkout * package com.google.api.client.googleapis.media; * */ package io.uploader.drive.drive.media; import com.google.api.client.http.HttpIOExceptionHandler; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpUnsuccessfulResponseHandler; import com.google.api.client.util.Beta; import com.google.api.client.util.Preconditions; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * MediaUpload error handler handles an {@link IOException} and an abnormal HTTP * response by calling to {@link MediaHttpUploader#serverErrorCallback()}. * * @author Eyal Peled */ @Beta class MediaUploadErrorHandler implements HttpUnsuccessfulResponseHandler, HttpIOExceptionHandler { private static final Logger logger = LoggerFactory .getLogger(MediaUploadErrorHandler.class); private final AtomicInteger httpErrorCounter ; /** The uploader to callback on if there is a server error. */ private final MediaHttpUploader uploader; /** The original {@link HttpIOExceptionHandler} of the HTTP request. */ private final HttpIOExceptionHandler originalIOExceptionHandler; /** * The original {@link HttpUnsuccessfulResponseHandler} of the HTTP request. */ private final HttpUnsuccessfulResponseHandler originalUnsuccessfulHandler; /** * Constructs a new instance from {@link MediaHttpUploader} and * {@link HttpRequest}. */ public MediaUploadErrorHandler(MediaHttpUploader uploader, HttpRequest request, AtomicInteger httpErrorCounter) { this.uploader = Preconditions.checkNotNull(uploader); originalIOExceptionHandler = request.getIOExceptionHandler(); originalUnsuccessfulHandler = request.getUnsuccessfulResponseHandler(); request.setIOExceptionHandler(this); request.setUnsuccessfulResponseHandler(this); this.httpErrorCounter = Preconditions.checkNotNull(httpErrorCounter) ; } @Override public boolean handleIOException(HttpRequest request, boolean supportsRetry) throws IOException { boolean handled = originalIOExceptionHandler != null && originalIOExceptionHandler.handleIOException(request, supportsRetry); // TODO(peleyal): figure out what is best practice - call // serverErrorCallback only if I/O // exception was handled, or call it regardless if (handled) { try { uploader.serverErrorCallback(); } catch (IOException e) { logger.warn("exception thrown while calling server callback", e); } } return handled; } private void exponentialBackoff(int n) { try { Thread.sleep((long) (Math.pow(2.0, n) * 1000 + Math.random() * 1000)); } catch (InterruptedException e) { logger.error("Error occurred while sleeping", e); } } private final int maxRetry = 5 ; @Override public boolean handleResponse(HttpRequest request, HttpResponse response, boolean supportsRetry) throws IOException { boolean handled = originalUnsuccessfulHandler != null && originalUnsuccessfulHandler.handleResponse(request, response, supportsRetry); // TODO(peleyal): figure out what is best practice - call // serverErrorCallback only if the // abnormal response was handled, or call it regardless int statusCode = response.getStatusCode() ; boolean retry = false ; StringBuilder sb = new StringBuilder () ; sb.append("Error ").append(statusCode).append(" (").append(response.getStatusMessage()).append(") occurred...") ; if (((statusCode < 600 && statusCode >= 500)) && httpErrorCounter.get() <= maxRetry) { // We need to resume the upload sb.append("attempting to resume the upload...") ; exponentialBackoff(httpErrorCounter.getAndIncrement()) ; retry = true ; } else if (statusCode != 308 && httpErrorCounter.getAndIncrement() < maxRetry) { // we try for other errors to resume the upload // (we do not need the exponentialBackoff) sb.append("attempting to resume the upload...") ; if (statusCode == 401) { // There is a nasty bug in the Drive API, which seems to be there for quite a while... // Basically after about one hour, the 401 error occurs, and there is not much we can do to resume // the upload... // https://code.google.com/p/google-api-python-client/issues/detail?id=231 retry = false ; } } if (statusCode != 308) { logger.info(sb.toString()); } if (handled && supportsRetry && retry /* && response.getStatusCode() / 100 == 5 */) { try { uploader.serverErrorCallback(); } catch (IOException e) { logger.warn("exception thrown while calling server callback", e); } } return handled; } }