
com.yandex.disk.rest.RestClientIO Maven / Gradle / Ivy
/*
* (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 - 2025 Weber Informatics LLC | Privacy Policy