com.yandex.disk.rest.RestClientIO.java Source code

Java tutorial

Introduction

Here is the source code for com.yandex.disk.rest.RestClientIO.java

Source

/*
* (C) 2015 Yandex LLC (https://yandex.com/)
*
* The source code of Java SDK for Yandex.Disk REST API
* is available to use under terms of Apache License,
* Version 2.0. See the file LICENSE for the details.
*/

package com.yandex.disk.rest;

import com.google.gson.Gson;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import com.yandex.disk.rest.exceptions.CancelledDownloadException;
import com.yandex.disk.rest.exceptions.DownloadNoSpaceAvailableException;
import com.yandex.disk.rest.exceptions.ServerIOException;
import com.yandex.disk.rest.exceptions.http.ConflictException;
import com.yandex.disk.rest.exceptions.http.FileNotModifiedException;
import com.yandex.disk.rest.exceptions.http.FileTooBigException;
import com.yandex.disk.rest.exceptions.http.HttpCodeException;
import com.yandex.disk.rest.exceptions.http.InsufficientStorageException;
import com.yandex.disk.rest.exceptions.http.NotFoundException;
import com.yandex.disk.rest.exceptions.http.PreconditionFailedException;
import com.yandex.disk.rest.exceptions.http.RangeNotSatisfiableException;
import com.yandex.disk.rest.exceptions.http.ServiceUnavailableException;
import com.yandex.disk.rest.json.Link;
import com.yandex.disk.rest.json.Operation;
import com.yandex.disk.rest.retrofit.ErrorHandlerImpl;
import com.yandex.disk.rest.util.Hash;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/* package */ class RestClientIO {

    private static final Logger logger = LoggerFactory.getLogger(RestClientIO.class);

    private static final String ETAG_HEADER = "Etag";
    private static final String SHA256_HEADER = "Sha256";
    private static final String SIZE_HEADER = "Size";
    private static final String CONTENT_LENGTH_HEADER = "Content-Length";
    private static final String CONTENT_RANGE_HEADER = "Content-Range";

    private static final String METHOD_GET = "GET";
    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_PUT = "PUT";

    private static final Pattern CONTENT_RANGE_HEADER_PATTERN = Pattern.compile("bytes\\D+(\\d+)-\\d+/(\\d+)");

    private OkHttpClient client;
    private List<CustomHeader> commonHeaders;

    /* package */ RestClientIO(OkHttpClient client, List<CustomHeader> commonHeaders) {
        this.client = client;
        this.commonHeaders = commonHeaders;
    }

    private Request.Builder buildRequest() {
        Request.Builder request = new Request.Builder();
        for (CustomHeader header : commonHeaders) {
            request.addHeader(header.getName(), header.getValue());
        }
        return request;
    }

    /* package */ void downloadUrl(final String url, final DownloadListener downloadListener)
            throws IOException, CancelledDownloadException, DownloadNoSpaceAvailableException, HttpCodeException {

        Request.Builder req = buildRequest().url(url);

        long length = downloadListener.getLocalLength();
        String ifTag = "If-None-Match";
        if (length >= 0) {
            ifTag = "If-Range";
            StringBuilder contentRange = new StringBuilder();
            contentRange.append("bytes=").append(length).append("-");
            logger.debug("Range: " + contentRange);
            req.addHeader("Range", contentRange.toString());
        }

        String etag = downloadListener.getETag();
        if (etag != null) {
            logger.debug(ifTag + ": " + etag);
            req.addHeader(ifTag, etag);
        }

        Request request = req.build();
        Response response = client.newCall(request).execute();

        boolean partialContent = false;
        int code = response.code();
        switch (code) {
        case 200:
            // OK
            break;
        case 206:
            partialContent = true;
            break;
        case 304:
            throw new FileNotModifiedException(code);
        case 404:
            throw new NotFoundException(code);
        case 416:
            throw new RangeNotSatisfiableException(code);
        default:
            throw new HttpCodeException(code);
        }

        ResponseBody responseBody = response.body();
        long contentLength = responseBody.contentLength();
        logger.debug("download: contentLength=" + contentLength);

        long loaded;
        if (partialContent) {
            ContentRangeResponse contentRangeResponse = parseContentRangeHeader(response.header("Content-Range"));
            logger.debug("download: contentRangeResponse=" + contentRangeResponse);
            if (contentRangeResponse != null) {
                loaded = contentRangeResponse.getStart();
                contentLength = contentRangeResponse.getSize();
            } else {
                loaded = length;
            }
        } else {
            loaded = 0;
            if (contentLength < 0) {
                contentLength = 0;
            }
        }

        OutputStream os = null;
        try {
            downloadListener.setStartPosition(loaded);
            MediaType contentTypeHeader = responseBody.contentType();
            if (contentTypeHeader != null) {
                downloadListener.setContentType(contentTypeHeader.toString());
            }
            downloadListener.setContentLength(contentLength);

            int count;
            InputStream content = responseBody.byteStream();
            os = downloadListener.getOutputStream(partialContent);
            final byte[] downloadBuffer = new byte[1024];
            while ((count = content.read(downloadBuffer)) != -1) {
                if (downloadListener.hasCancelled()) {
                    logger.info("Downloading " + url + " canceled");
                    client.cancel(request.tag());
                    throw new CancelledDownloadException();
                }
                os.write(downloadBuffer, 0, count);
                loaded += count;
                downloadListener.updateProgress(loaded, contentLength);
            }
        } catch (CancelledDownloadException ex) {
            throw ex;
        } catch (Exception e) {
            logger.warn(e.getMessage(), e);
            client.cancel(request.tag());
            if (e instanceof IOException) {
                throw (IOException) e;
            } else if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else if (e instanceof DownloadNoSpaceAvailableException) {
                throw (DownloadNoSpaceAvailableException) e;
            } else {
                // never happen
                throw new RuntimeException(e);
            }
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException ex) {
                // nothing
            }
            try {
                response.body().close();
            } catch (IOException | NullPointerException ex) {
                logger.warn(ex.getMessage(), ex);
            }
        }
    }

    private ContentRangeResponse parseContentRangeHeader(String header) {
        if (header == null) {
            return null;
        }
        Matcher matcher = CONTENT_RANGE_HEADER_PATTERN.matcher(header);
        if (!matcher.matches()) {
            return null;
        }
        try {
            return new ContentRangeResponse(Long.parseLong(matcher.group(1)), Long.parseLong(matcher.group(2)));
        } catch (IllegalStateException ex) {
            logger.error("parseContentRangeHeader: " + header, ex);
            return null;
        } catch (NumberFormatException ex) {
            logger.error("parseContentRangeHeader: " + header, ex);
            return null;
        }
    }

    /* package */ void uploadFile(final String url, final File file, final long startOffset,
            final ProgressListener progressListener) throws IOException, HttpCodeException {
        logger.debug("uploadFile: put to url: " + url);
        MediaType mediaType = MediaType.parse("application/octet-stream");
        RequestBody requestBody = RequestBodyProgress.create(mediaType, file, startOffset, progressListener);
        Request.Builder requestBuilder = buildRequest().removeHeader(Credentials.AUTHORIZATION_HEADER).url(url)
                .put(requestBody);
        if (startOffset > 0) {
            StringBuilder contentRange = new StringBuilder();
            contentRange.append("bytes ").append(startOffset).append("-").append(file.length() - 1).append("/")
                    .append(file.length());
            logger.debug(CONTENT_RANGE_HEADER + ": " + contentRange);
            requestBuilder.addHeader(CONTENT_RANGE_HEADER, contentRange.toString());
        }
        Request request = requestBuilder.build();

        Response response = client.newCall(request).execute();

        String statusLine = response.message();
        logger.debug("headUrl: " + statusLine + " for url " + url);

        int code = response.code();

        ResponseBody responseBody = response.body();
        responseBody.close();

        switch (code) {
        case 201:
        case 202:
            logger.debug("uploadFile: file uploaded successfully: " + file);
            break;
        case 404:
            throw new NotFoundException(code, null);
        case 409:
            throw new ConflictException(code, null);
        case 412:
            throw new PreconditionFailedException(code, null);
        case 413:
            throw new FileTooBigException(code, null);
        case 503:
            throw new ServiceUnavailableException(code, null);
        case 507:
            throw new InsufficientStorageException(code, null);
        default:
            throw new HttpCodeException(code);
        }
    }

    /* package */ long getUploadedSize(String url, Hash hash) throws IOException {

        Request request = buildRequest().removeHeader(Credentials.AUTHORIZATION_HEADER).url(url).head()
                .addHeader(ETAG_HEADER, hash.getMd5()).addHeader(SHA256_HEADER, hash.getSha256())
                .addHeader(SIZE_HEADER, String.valueOf(hash.getSize())).build();

        Response response = client.newCall(request).execute();

        int code = response.code();
        ResponseBody responseBody = response.body();
        responseBody.close();
        switch (code) {
        case 200:
            return Long.valueOf(response.header(CONTENT_LENGTH_HEADER, "0"));
        default:
            return 0;
        }
    }

    /* package */ Operation getOperation(String url) throws IOException, HttpCodeException {
        Response response = call(METHOD_GET, url);
        int code = response.code();
        if (!response.isSuccessful()) {
            throw new HttpCodeException(code);
        }
        return parseJson(response, Operation.class);
    }

    /* package */ Link delete(String url) throws IOException, ServerIOException {
        Response response = null;
        try {
            response = call(METHOD_DELETE, url);
            switch (response.code()) {
            case 202:
                Link result = parseJson(response, Link.class);
                result.setHttpStatus(Link.HttpStatus.inProgress);
                return result;
            case 204:
                close(response);
                return Link.DONE;
            default:
                throw ErrorHandlerImpl.createHttpCodeException(response.code(), response.body().byteStream());
            }
        } finally {
            close(response);
        }
    }

    /* package */ Link put(String url) throws IOException, ServerIOException {
        Response response = null;
        try {
            response = call(METHOD_PUT, url);
            switch (response.code()) {
            case 201:
                Link done = parseJson(response, Link.class);
                done.setHttpStatus(Link.HttpStatus.done);
                return done;
            case 202:
                Link inProgress = parseJson(response, Link.class);
                inProgress.setHttpStatus(Link.HttpStatus.inProgress);
                return inProgress;
            default:
                throw ErrorHandlerImpl.createHttpCodeException(response.code(), response.body().byteStream());
            }
        } finally {
            close(response);
        }
    }

    private void close(Response response) throws IOException {
        if (response == null) {
            return;
        }
        ResponseBody responseBody = response.body();
        if (responseBody == null) {
            return;
        }

        responseBody.close();
    }

    private Response call(String method, String url) throws IOException {
        Request request = buildRequest().method(method, null).url(url).build();
        return client.newCall(request).execute();
    }

    private <T> T parseJson(Response response, Class<T> classOfT) throws IOException {
        ResponseBody responseBody = null;
        try {
            responseBody = response.body();
            Gson gson = new Gson();
            return gson.fromJson(responseBody.charStream(), classOfT);
        } finally {
            if (responseBody != null) {
                responseBody.close();
            }
        }
    }

    private static class ContentRangeResponse {

        private final long start, size;

        ContentRangeResponse(long start, long size) {
            this.start = start;
            this.size = size;
        }

        long getStart() {
            return start;
        }

        long getSize() {
            return size;
        }
    }
}