All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.yandex.disk.rest.RestClientIO Maven / Gradle / Ivy

Go to download

The Java SDK for Yandex.Disk REST API is intended for applications that work with the files of Yandex.Disk users or store their own files and settings on Yandex.Disk.

The newest version!
/*
* (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 commonHeaders;

    /* package */ RestClientIO(OkHttpClient client, List 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 parseJson(Response response, Class 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;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy