public abstract class ServerException extends Exception {

    public ServerException() {
        super();
    }

    public ServerException(String detailMessage) {
        super(detailMessage);
    }

    public ServerException(String detailMessage, Throwable throwable) {
        super(detailMessage, throwable);
    }

    public ServerException(Throwable throwable) {
        super(throwable);
    }
} public class ServerIOException extends ServerException {

    public ServerIOException() {
        super();
    }

    public ServerIOException(String message) {
        super(message);
    }

    public ServerIOException(Throwable e) {
        super(e);
    }
} public class WrongMethodException extends ServerException {
    public WrongMethodException(String message) {
        super(message);
    }
} public class HttpCodeException extends ServerIOException {

    protected final int code;
    protected final ApiError response;

    public HttpCodeException(int code, ApiError response) {
        super();
        this.code = code;
        this.response = response;
    }

    public HttpCodeException(int code) {
        this(code, null);
    }

    public int getCode() {
        return code;
    }

    public ApiError getResponse() {
        return response;
    }

    @Override
    public String toString() {
        return "HttpCodeException{" +
                "code=" + code +
                ", response=" + response +
                '}';
    }
} public class ApiVersion {

    @SerializedName("build")
    String build;

    @SerializedName("api_version")
    String apiVersion;

    public String getBuild() {
        return build;
    }

    public String getApiVersion() {
        return apiVersion;
    }

    @Override
    public String toString() {
        return "ApiVersion{" +
                "build='" + build + '\'' +
                ", apiVersion='" + apiVersion + '\'' +
                '}';
    }
} public class DiskInfo {

    @SerializedName("trash_size")
    long trashSize;

    @SerializedName("total_space")
    long totalSpace;

    @SerializedName("used_space")
    long usedSpace;

    @SerializedName("system_folders")
    Map<String,String> systemFolders;

    public long getTrashSize() {
        return trashSize;
    }

    public long getTotalSpace() {
        return totalSpace;
    }

    public long getUsedSpace() {
        return usedSpace;
    }

    public Map<String, String> getSystemFolders() {
        return systemFolders;
    }

    @Override
    public String toString() {
        return "DiskCapacity{" +
                "trashSize=" + trashSize +
                ", totalSpace=" + totalSpace +
                ", usedSpace=" + usedSpace +
                ", systemFolders=" + systemFolders +
                '}';
    }
} public class Link {

    public enum HttpStatus {
        done, inProgress, error
    }

    HttpStatus httpStatus;

    public final static Link DONE = new Link() {
        {
            httpStatus = HttpStatus.done;
        }
    };

    @SerializedName("href")
    String href;

    @SerializedName("method")
    String method;

    @SerializedName("templated")
    boolean templated;

    public String getHref() {
        return href;
    }

    public String getMethod() {
        return method;
    }

    public boolean isTemplated() {
        return templated;
    }

    public HttpStatus getHttpStatus() {
        return httpStatus;
    }

    public void setHttpStatus(HttpStatus httpStatus) {
        this.httpStatus = httpStatus;
    }

    @Override
    public String toString() {
        return "Link{" +
                "href='" + href + '\'' +
                ", method='" + method + '\'' +
                ", templated=" + templated +
                ", httpStatus=" + httpStatus +
                '}';
    }
} public class Operation {

    private static final String IN_PROGRESS = "in-progress";
    private static final String SUCCESS = "success";

    @SerializedName("status")
    String status;

    public String getStatus() {
        return status;
    }

    public boolean isInProgress() {
        return IN_PROGRESS.equals(status);
    }

    public boolean isSuccess() {
        return SUCCESS.equals(status);
    }

    @Override
    public String toString() {
        return "Operation{" +
                "status='" + status + '\'' +
                '}';
    }
} public class Resource {

    @SerializedName("public_key")
    String publicKey;

    @SerializedName("_embedded")
    ResourceList resourceList;

    @SerializedName("name")
    String name;

    @SerializedName("created")
    String created;

    @SerializedName("public_url")
    String publicUrl;

    @SerializedName("origin_path")
    String originPath;

    @SerializedName("modified")
    String modified;

    @SerializedName("deleted")
    String deleted;

    @SerializedName("path")
    String path;

    @SerializedName("md5")
    String md5;

    @SerializedName("type")
    String type;

    @SerializedName("mime_type")
    String mimeType;

    @SerializedName("media_type")
    String mediaType;

    @SerializedName("preview")
    String preview;

    @SerializedName("size")
    long size;

    @SerializedName("custom_properties")
    Object properties;

    public String getPublicKey() {
        return publicKey;
    }

    public ResourceList getResourceList() {
        return resourceList;
    }

    public String getName() {
        return name;
    }

    public Date getCreated() {
        return created != null ? ISO8601.parse(created) : null;
    }

    public String getPublicUrl() {
        return publicUrl;
    }

    public ResourcePath getOriginPath() {
        return originPath != null ? new ResourcePath(originPath) : null;
    }

    public Date getModified() {
        return modified != null ? ISO8601.parse(modified) : null;
    }

    public Date getDeleted() {
        return deleted != null ? ISO8601.parse(deleted) : null;
    }

    public ResourcePath getPath() {
        return path != null ? new ResourcePath(path) : null;
    }

    public String getMd5() {
        return md5;
    }

    public String getType() {
        return type;
    }

    public boolean isDir() {
        return "dir".equalsIgnoreCase(type);
    }

    public String getMimeType() {
        return mimeType;
    }

    public String getMediaType() {
        return mediaType;
    }

    public String getPreview() {
        return preview;
    }

    public long getSize() {
        return size;
    }

    public Object getProperties() {
        return properties;
    }

    @Override
    public String toString() {
        return "Resource{" +
                "publicKey='" + publicKey + '\'' +
                ", resourceList=" + resourceList +
                ", name='" + name + '\'' +
                ", created='" + getCreated() + '\'' +
                ", publicUrl='" + publicUrl + '\'' +
                ", originPath='" + getOriginPath() + '\'' +
                ", modified='" + getModified() + '\'' +
                ", deleted='" + getDeleted() + '\'' +
                ", path='" + getPath() + '\'' +
                ", md5='" + md5 + '\'' +
                ", type='" + type + '\'' +
                ", mimeType='" + mimeType + '\'' +
                ", mediaType='" + mediaType + '\'' +
                ", preview='" + preview + '\'' +
                ", size=" + size +
                ", properties=" + properties +
                '}';
    }
} public class ResourceList {

    @SerializedName("sort")
    String sort;

    @SerializedName("public_key")
    String publicKey;

    @SerializedName("items")
    List<Resource> items;

    @SerializedName("path")
    String path;

    @SerializedName("limit")
    int limit;

    @SerializedName("offset")
    int offset;

    @SerializedName("total")
    int total;

    public String getSort() {
        return sort;
    }

    public String getPublicKey() {
        return publicKey;
    }

    public List<Resource> getItems() {
        return items;
    }

    public String getPath() {
        return path;
    }

    public int getLimit() {
        return limit;
    }

    public int getOffset() {
        return offset;
    }

    public int getTotal() {
        return total;
    }

    @Override
    public String toString() {
        return "ResourceList{" +
                "sort='" + sort + '\'' +
                ", publicKey='" + publicKey + '\'' +
                ", items=" + items +
                ", path='" + path + '\'' +
                ", limit=" + limit +
                ", offset=" + offset +
                ", total=" + total +
                '}';
    }
} public interface CloudApi {

    @GET("/")
    ApiVersion getApiVersion()
            throws IOException, ServerIOException;

    @GET("/v1/disk/operations/{operation_id}")
    Operation getOperation(@Path("operation_id") String operationId)
            throws IOException, ServerIOException;

    @GET("/v1/disk")
    DiskInfo getDiskInfo(@Query("fields") String fields)
            throws IOException, ServerIOException;

    @GET("/v1/disk/resources")
    Resource getResources(@Query("path") String path, @Query("fields") String fields,
                          @Query("limit") Integer limit, @Query("offset") Integer offset,
                          @Query("sort") String sort, @Query("preview_size") String previewSize,
                          @Query("preview_crop") Boolean previewCrop)
            throws IOException, ServerIOException;

    @GET("/v1/disk/resources/files")
    ResourceList getFlatResourceList(@Query("limit") Integer limit, @Query("media_type") String mediaType,
                                     @Query("offset") Integer offset, @Query("fields") String fields,
                                     @Query("preview_size") String previewSize,
                                     @Query("preview_crop") Boolean previewCrop)
            throws IOException, ServerIOException;

    @GET("/v1/disk/resources/last-uploaded")
    ResourceList getLastUploadedResources(@Query("limit") Integer limit, @Query("media_type") String mediaType,
                                          @Query("offset") Integer offset, @Query("fields") String fields,
                                          @Query("preview_size") String previewSize,
                                          @Query("preview_crop") Boolean previewCrop)
            throws IOException, ServerIOException;

    @PATCH("/v1/disk/resources/")
    Resource patchResource(@Query("path") String path, @Query("fields") String fields,
                           @Body TypedOutput body)
            throws IOException, ServerIOException;

    @GET("/v1/disk/resources/download")
    Link getDownloadLink(@Query("path") String path)
            throws IOException, ServerIOException;

    @POST("/v1/disk/resources/upload")
    Link saveFromUrl(@Query("url") String url, @Query("path") String path)
            throws IOException, ServerIOException;

    @GET("/v1/disk/resources/upload")
    Link getUploadLink(@Query("path") String path, @Query("overwrite") Boolean overwrite)
            throws IOException, ServerIOException;

    @POST("/v1/disk/resources/copy")
    Link copy(@Query("from") String from, @Query("path") String path,
              @Query("overwrite") Boolean overwrite)
            throws IOException, ServerIOException;

    @POST("/v1/disk/resources/move")
    Link move(@Query("from") String from, @Query("path") String path,
              @Query("overwrite") Boolean overwrite)
            throws IOException, ServerIOException;

    @PUT("/v1/disk/resources")
    Link makeFolder(@Query("path") String path)
            throws IOException, ServerIOException;

    @PUT("/v1/disk/resources/publish")
    Link publish(@Query("path") String path)
            throws IOException, ServerIOException;

    @PUT("/v1/disk/resources/unpublish")
    Link unpublish(@Query("path") String path)
            throws IOException, ServerIOException;

    @GET("/v1/disk/public/resources")
    Resource listPublicResources(@Query("public_key") String publicKey, @Query("path") String path,
                                 @Query("fields") String fields, @Query("limit") Integer limit,
                                 @Query("offset") Integer offset, @Query("sort") String sort,
                                 @Query("preview_size") String previewSize,
                                 @Query("preview_crop") Boolean previewCrop)
            throws IOException, ServerIOException;

    @GET("/v1/disk/public/resources/download")
    Link getPublicResourceDownloadLink(@Query("public_key") String publicKey,
                                       @Query("path") String path)
            throws IOException, ServerIOException;

    @POST("/v1/disk/public/resources/save-to-disk/")
    Link savePublicResource(@Query("public_key") String publicKey, @Query("path") String path,
                            @Query("name") String name)
            throws IOException, ServerIOException;

    @GET("/v1/disk/trash/resources")
    Resource getTrashResources(@Query("path") String path, @Query("fields") String fields,
                               @Query("limit") Integer limit, @Query("offset") Integer offset,
                               @Query("sort") String sort, @Query("preview_size") String previewSize,
                               @Query("preview_crop") Boolean previewCrop)
            throws IOException, ServerIOException;
} public class ErrorHandlerImpl implements ErrorHandler {

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

    @Override
    public Throwable handleError(RetrofitError retrofitError) {
        RetrofitError.Kind kind = retrofitError.getKind();
        switch (kind) {
            case NETWORK:
                Throwable th = retrofitError.getCause();
                return th instanceof IOException ? th : new IOException(th);

            case CONVERSION:
                return new RetrofitConversionException(retrofitError.getCause());

            case HTTP:
                try {
                    Response response = retrofitError.getResponse();
                    int httpCode = response.getStatus();
                    return createHttpCodeException(httpCode, response.getBody().in());
                } catch (IOException ex) {
                    logger.debug("errorHandler", retrofitError);
                    return ex;
                }

            case UNEXPECTED:
                return new ServerIOException(retrofitError.getCause());

            default:
                return new ServerIOException("ErrorHandler: unhandled error " + kind.name());
        }
    }

    public static HttpCodeException createHttpCodeException(int httpCode, InputStream in) {
        return createHttpCodeException(httpCode, readApiError(in));
    }

    private static ApiError readApiError(InputStream in) {
        Reader reader = new InputStreamReader(in);
        return new Gson().fromJson(reader, ApiError.class);
    }

    private static HttpCodeException createHttpCodeException(int httpCode, ApiError apiError) {
        logger.debug("getStatus=" + httpCode);
        switch (httpCode) {
            case 400:
                return new BadRequestException(httpCode, apiError);
            case 401:
                return new UnauthorizedException(httpCode, apiError);
            case 403:
                return new ForbiddenException(httpCode, apiError);
            case 404:
                return new NotFoundException(httpCode, apiError);
            case 405:
                return new MethodNotAllowedException(httpCode, apiError);
            case 406:
                return new NotAcceptableException(httpCode, apiError);
            case 409:
                return new ConflictException(httpCode, apiError);
            case 410:
                return new GoneException(httpCode, apiError);
            case 412:
                return new PreconditionFailedException(httpCode, apiError);
            case 413:
                return new FileTooBigException(httpCode, apiError);
            case 415:
                return new UnsupportedMediaTypeException(httpCode, apiError);
            case 422:
                return new UnprocessableEntityException(httpCode, apiError);
            case 423:
                return new LockedException(httpCode, apiError);
            case 429:
                return new TooManyRequestsException(httpCode, apiError);
            case 500:
                return new InternalServerException(httpCode, apiError);
            case 501:
                return new NotImplementedException(httpCode, apiError);
            case 502:
                return new BadGatewayException(httpCode, apiError);
            case 503:
                return new ServiceUnavailableException(httpCode, apiError);
            case 507:
                return new InsufficientStorageException(httpCode, apiError);
            default:
                return new HttpCodeException(httpCode, apiError);
        }
    }
} public class RequestInterceptorImpl implements RequestInterceptor {

    private final List<CustomHeader> headers;

    public RequestInterceptorImpl(final List<CustomHeader> headers) {
        this.headers = headers;
    }

    @Override
    public void intercept(final RequestFacade request) {
        for (CustomHeader header : headers) {
            addHeader(request, header);
        }
    }

    private static void addHeader(final RequestFacade request, final CustomHeader header) {
        request.addHeader(header.getName(), header.getValue());
    }
} public class Hash {

    private enum HashType {

        MD5("MD5"), SHA256("SHA-256");

        private final String value;

        HashType(String value) {
            this.value = value;
        }

        String getValue() {
            return value;
        }
    }

    private final String md5, sha256;
    private final long size;

    public Hash(String md5, String sha256, long size) {
        this.md5 = md5;
        this.sha256 = sha256;
        this.size = size;
    }

    public static Hash getHash(File file)
            throws IOException {
        InputStream is = new FileInputStream(file);
        try {
            return getHash(is, file.length());
        } finally {
            close(is);
        }
    }

    private static Hash getHash(InputStream is, long size)
            throws IOException {
        MessageDigest md5Digest, sha256Digest;
        try {
            md5Digest = MessageDigest.getInstance(HashType.MD5.getValue());
            sha256Digest = MessageDigest.getInstance(HashType.SHA256.getValue());
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
        byte[] buf = new byte[8192];
        int count;
        while ((count = is.read(buf)) > 0) {
            md5Digest.update(buf, 0, count);
            sha256Digest.update(buf, 0, count);
        }
        return new Hash(toString(md5Digest.digest()), toString(sha256Digest.digest()), size);
    }

    private static void close(InputStream is)
            throws IOException {
        if (is != null) {
            is.close();
        }
    }

    public String getMd5() {
        return md5;
    }

    public String getSha256() {
        return sha256;
    }

    public long getSize() {
        return size;
    }

    public static String toString(byte[] bytes) {
        if (bytes == null) {
            return null;
        }
        StringBuilder out = new StringBuilder();
        for (byte b : bytes) {
            String n = Integer.toHexString(b & 0x000000FF);
            if (n.length() == 1) {
                out.append('0');
            }
            out.append(n);
        }
        return out.toString();
    }

    @Override
    public String toString() {
        return "Hash{" +
                "md5='" + md5 + '\'' +
                ", sha256='" + sha256 + '\'' +
                ", size=" + size +
                '}';
    }
}
import com.squareup.okhttp.OkHttpClient;
import com.yandex.disk.rest.exceptions.ServerException;
import com.yandex.disk.rest.exceptions.ServerIOException;
import com.yandex.disk.rest.exceptions.WrongMethodException;
import com.yandex.disk.rest.exceptions.http.HttpCodeException;
import com.yandex.disk.rest.json.ApiVersion;
import com.yandex.disk.rest.json.DiskInfo;
import com.yandex.disk.rest.json.Link;
import com.yandex.disk.rest.json.Operation;
import com.yandex.disk.rest.json.Resource;
import com.yandex.disk.rest.json.ResourceList;
import com.yandex.disk.rest.retrofit.CloudApi;
import com.yandex.disk.rest.retrofit.ErrorHandlerImpl;
import com.yandex.disk.rest.retrofit.RequestInterceptorImpl;
import com.yandex.disk.rest.util.Hash;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import retrofit.RestAdapter;
import retrofit.client.OkClient;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

    /**
     * Cleaning the Trash
     *
     * @see <p>API reference <a href="http://api.yandex.com/disk/api/reference/trash-delete.xml">english</a>,
     * <a href="https://tech.yandex.ru/disk/api/reference/trash-delete-docpage/">russian</a></p>
     */
    public Link deleteFromTrash(final String path)
            throws IOException, ServerIOException {
        return new RestClientIO(client, credentials.getHeaders())
                .delete(new QueryBuilder(getUrl() + "/v1/disk/trash/resources")
                        .add("path", path)
                        .build());
    }

    /**
     * Restoring a file or folder from the Trash
     *
     * @see <p>API reference <a href="http://api.yandex.com/disk/api/reference/trash-restore.xml">english</a>,
     * <a href="https://tech.yandex.ru/disk/api/reference/trash-restore-docpage/">russian</a></p>
     */
    public Link restoreFromTrash(final String path, final String name, final Boolean overwrite)
            throws IOException, ServerIOException {
        return new RestClientIO(client, credentials.getHeaders())
                .put(new QueryBuilder(getUrl() + "/v1/disk/trash/resources/restore")
                        .add("path", path)
                        .add("name", name)
                        .add("overwrite", overwrite)
                        .build());
    }

    private void parseListResponse(final Resource resource, final ResourcesHandler handler) {
        handler.handleSelf(resource);
        ResourceList items = resource.getResourceList();
        int size = 0;
        if (items != null) {
            size = items.getItems().size();
            for (Resource item : items.getItems()) {
                handler.handleItem(item);
            }
        }
        handler.onFinished(size);
    }

    private void parseListResponse(final ResourceList resourceList, final ResourcesHandler handler) {
        List<Resource> items = resourceList.getItems();
        int size = 0;
        if (items != null) {
            size = items.size();
            for (Resource item : items) {
                handler.handleItem(item);
            }
        }
        handler.onFinished(size);
    }

    /**
     * Downloading a file from Disk
     *
     * @see <p>API reference <a href="http://api.yandex.com/disk/api/reference/content.xml">english</a>,
     * <a href="https://tech.yandex.ru/disk/api/reference/content-docpage/">russian</a></p>
     */
    public void downloadFile(final String path, final File saveTo, final ProgressListener progressListener)
            throws IOException, ServerException {
        Link link = cloudApi.getDownloadLink(path);
        new RestClientIO(client, credentials.getHeaders())
                .downloadUrl(link.getHref(), new FileDownloadListener(saveTo, progressListener));
    }

    /**
     * Downloading a file from Disk
     *
     * @see <p>API reference <a href="http://api.yandex.com/disk/api/reference/content.xml">english</a>,
     * <a href="https://tech.yandex.ru/disk/api/reference/content-docpage/">russian</a></p>
     */
    public void downloadFile(final String path, final DownloadListener downloadListener)
            throws IOException, ServerException {
        Link link = cloudApi.getDownloadLink(path);
        new RestClientIO(client, credentials.getHeaders())
                .downloadUrl(link.getHref(), downloadListener);
    }

    /**
     * Uploading a file to Disk from external resource
     *
     * @see <p>API reference <a href="http://api.yandex.com/disk/api/reference/upload-ext.xml">english</a>,
     * <a href="https://tech.yandex.ru/disk/api/reference/upload-ext-docpage/">russian</a></p>
     */
    public Link saveFromUrl(final String url, final String serverPath)
            throws ServerIOException, IOException {
        return cloudApi.saveFromUrl(url, serverPath);
    }

    /**
     * Uploading a file to Disk: get Link to upload
     *
     * @see <p>API reference <a href="http://api.yandex.com/disk/api/reference/upload.xml">english</a>,
     * <a href="https://tech.yandex.ru/disk/api/reference/upload-docpage/">russian</a></p>
     */
    public Link getUploadLink(final String serverPath, final boolean overwrite)
            throws ServerIOException, WrongMethodException, IOException {
        Link link = cloudApi.getUploadLink(serverPath, overwrite);
        if (!"PUT".equalsIgnoreCase(link.getMethod())) {
            throw new WrongMethodException("Method in Link object is not PUT");
        }
        return link;
    }

    /**
     * Uploading a file to Disk: upload a file
     *
     * @see <p>API reference <a href="http://api.yandex.com/disk/api/reference/upload.xml">english</a>,
     * <a href="https://tech.yandex.ru/disk/api/reference/upload-docpage/">russian</a></p>
     */
    public void uploadFile(final Link link, final boolean resumeUpload, final File localSource,
                           final ProgressListener progressListener)
            throws IOException, ServerException {
        RestClientIO clientIO = new RestClientIO(client, credentials.getHeaders());
        long startOffset = 0;
        if (resumeUpload) {
            Hash hash = Hash.getHash(localSource);