package org.liberty.android.fantastischmemo.downloader.dropbox;
import android.support.annotation.NonNull;
import org.apache.commons.io.FileUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.liberty.android.fantastischmemo.common.AMApplication;
import org.liberty.android.fantastischmemo.common.AMEnv;
import org.liberty.android.fantastischmemo.R;
import org.liberty.android.fantastischmemo.downloader.common.DownloadItem;
import org.liberty.android.fantastischmemo.downloader.dropbox.entity.UserInfo;
import org.liberty.android.fantastischmemo.modules.ForApplication;
import org.liberty.android.fantastischmemo.utils.AMFileUtil;
import org.liberty.android.fantastischmemo.utils.RecentListUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import io.reactivex.Completable;
import io.reactivex.CompletableEmitter;
import io.reactivex.CompletableOnSubscribe;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@ForApplication
public class DropboxApiHelper {
private static final String USER_INFO_ENDPOINT = "https://api.dropboxapi.com/2/users/get_current_account";
private static final String CREATE_FOLDER_ENDPOINT = "https://api.dropboxapi.com/2/files/create_folder";
private static final String LIST_FOLDER_ENDPOINT = "https://api.dropboxapi.com/2/files/list_folder";
private static final String CONTINUE_LIST_FOLDER_ENDPOINT = "https://api.dropboxapi.com/2/files/list_folder/continue";
private static final String TEMPORARY_LINK_ENDPOINT = "https://api.dropboxapi.com/2/files/get_temporary_link";
private static final String UPLOAD_ENDPOINT = "https://content.dropboxapi.com/2/files/upload";
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
private static final MediaType OCTET_STREAM_TYPE = MediaType.parse("application/octet-stream");
private final AMFileUtil amFileUtil;
private final OkHttpClient okHttpClient;
private final AMApplication application;
private final RecentListUtil recentListUtil;
@Inject
public DropboxApiHelper(@NonNull AMApplication application,
@NonNull AMFileUtil amFileUtil,
@NonNull OkHttpClient okHttpClient,
@NonNull RecentListUtil recentListUtil) {
this.amFileUtil = amFileUtil;
this.okHttpClient = okHttpClient;
this.application = application;
this.recentListUtil = recentListUtil;
}
public Single<UserInfo> getUserInfo(@NonNull final String token) {
return Single.create(new SingleOnSubscribe<UserInfo>() {
@Override
public void subscribe(@NonNull final SingleEmitter<UserInfo> emitter) throws Exception {
RequestBody requestBody = RequestBody.create(null, new byte[0]);
Request request = new Request.Builder()
.url(USER_INFO_ENDPOINT)
.addHeader("Authorization", "Bearer " + token)
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
emitter.onError(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
emitter.onError(new IOException(getResponseErrorString(call.request(), response)));
return;
}
UserInfo userInfo = new UserInfo();
try {
JSONObject userInfoObject = new JSONObject(response.body().string());
userInfo.accountId = userInfoObject.getString("account_id");
userInfo.email = userInfoObject.getString("email");
JSONObject nameObject = userInfoObject.getJSONObject("name");
userInfo.displayName = nameObject.getString("display_name");
emitter.onSuccess(userInfo);
} catch (JSONException e) {
emitter.onError(e);
}
}
});
}
});
}
public Completable createFolder(@NonNull final String token, @NonNull final String folderName) {
return Completable.create(new CompletableOnSubscribe() {
@Override
public void subscribe(final CompletableEmitter emitter) throws Exception {
RequestBody requestBody = RequestBody.create(JSON_TYPE, String.format("{\"path\": \"/%1$s\",\"autorename\": false}",
folderName));
Request request = new Request.Builder()
.url(CREATE_FOLDER_ENDPOINT)
.addHeader("Authorization", "Bearer " + token)
.post(requestBody)
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
emitter.onError(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
emitter.onComplete();
}
});
}
});
}
public Observable<List<DownloadItem>> listFiles(@NonNull final String token, @NonNull final String folderName) {
return Observable.create(new ObservableOnSubscribe<List<DownloadItem>>() {
@Override
public void subscribe(ObservableEmitter<List<DownloadItem>> emitter) throws Exception {
RequestBody requestBody = RequestBody.create(JSON_TYPE, String.format(
"{\"path\": \"/%1$s\",\"recursive\": false,\"include_media_info\": false,\"include_deleted\": false,\"include_has_explicit_shared_members\": false}",
folderName));
Request request = new Request.Builder()
.url(LIST_FOLDER_ENDPOINT)
.addHeader("Authorization", "Bearer " + token)
.post(requestBody)
.build();
Response response = okHttpClient.newCall(request).execute();
if (!response.isSuccessful()) {
emitter.onError(new IOException(getResponseErrorString(request, response)));
return;
}
JSONObject listFolderObject = new JSONObject(response.body().string());
boolean hasMore = listFolderObject.getBoolean("has_more");
String cursor = listFolderObject.getString("cursor");
JSONArray entryArray = listFolderObject.getJSONArray("entries");
emitter.onNext(parseListFolderResponse(entryArray));
// Now use continuation token to emit paginated results
while (hasMore) {
RequestBody continueRequestBody = RequestBody.create(JSON_TYPE, String.format(
"{\"cursor\": \"%1$s\"}",
cursor));
Request continueRequest = new Request.Builder()
.url(CONTINUE_LIST_FOLDER_ENDPOINT)
.addHeader("Authorization", "Bearer " + token)
.post(continueRequestBody)
.build();
Response continueResponse = okHttpClient.newCall(continueRequest).execute();
if (!continueResponse.isSuccessful()) {
emitter.onError(new IOException(getResponseErrorString(continueRequest, continueResponse)));
return;
}
JSONObject continueListFolderObject = new JSONObject(continueResponse.body().string());
hasMore = continueListFolderObject.getBoolean("has_more");
cursor = continueListFolderObject.getString("cursor");
JSONArray continueEntryArray = continueListFolderObject.getJSONArray("entries");
emitter.onNext(parseListFolderResponse(continueEntryArray));
}
emitter.onComplete();
}
});
}
public Single<String> downloadFile(@NonNull final String token, @NonNull final String filePath) {
return Single.fromCallable(new Callable<String>() {
@Override
public String call() throws Exception {
RequestBody requestBody = RequestBody.create(JSON_TYPE, String.format(
"{\"path\": \"%1$s\"}",
filePath));
Request request = new Request.Builder()
.url(TEMPORARY_LINK_ENDPOINT)
.addHeader("Authorization", "Bearer " + token)
.post(requestBody)
.build();
Response response = okHttpClient.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException(getResponseErrorString(request, response));
}
JSONObject temporaryLinkObject = new JSONObject(response.body().string());
String downloadLink = temporaryLinkObject.getString("link");
JSONObject metadataObject = temporaryLinkObject.getJSONObject("metadata");
String fileName = metadataObject.getString("name");
Request downloadRequest = new Request.Builder()
.url(downloadLink)
.get()
.build();
Response downloadResponse = okHttpClient.newCall(downloadRequest).execute();
InputStream inputStream = downloadResponse.body().byteStream();
File outputFile = new File(AMEnv.DEFAULT_ROOT_PATH + fileName);
FileUtils.copyInputStreamToFile(inputStream, outputFile);
recentListUtil.addToRecentList(outputFile.getAbsolutePath());
return outputFile.getAbsolutePath();
}
});
}
public Completable uploadDropbox(@NonNull final String token, @NonNull final File fileToUpload, @NonNull final String uploadPath) {
return Completable.fromCallable(new Callable<Void>() {
@Override
public Void call() throws Exception {
if (!fileToUpload.exists()) {
throw new FileNotFoundException("Could not find file: " + fileToUpload.getAbsolutePath());
}
RequestBody requestBody = RequestBody.create(OCTET_STREAM_TYPE, fileToUpload);
Request request = new Request.Builder()
.url(UPLOAD_ENDPOINT)
.addHeader("Authorization", "Bearer " + token)
.addHeader("Dropbox-API-Arg", String.format("{\"path\": \"%1$s\",\"mode\": \"add\",\"autorename\": true,\"mute\": false}",
uploadPath))
.post(requestBody)
.build();
Response response = okHttpClient.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException(getResponseErrorString(request, response));
}
return null;
}
});
}
private String getResponseErrorString(Request request, Response response) throws IOException {
return String.format(Locale.US, "HTTP request: %1$s, response code: %2$d, response: %3$s",
request.url(),
response.code(),
response.body().string()
);
}
private List<DownloadItem> parseListFolderResponse(@NonNull JSONArray entryArray) throws JSONException {
List<DownloadItem> downloadItems = new ArrayList<>(entryArray.length());
for (int i = 0; i < entryArray.length(); i++) {
JSONObject entryObject = entryArray.getJSONObject(i);
String tag = entryObject.getString(".tag");
// We only list files here, folders are ignored
if (!"file".equals(tag)) {
continue;
}
DownloadItem downloadItem = new DownloadItem();
downloadItem.setTitle(entryObject.getString("name"));
downloadItem.setAddress(entryObject.getString("path_display"));
downloadItem.setDescription(String.format(application.getString(R.string.dropbox_item_description),
entryObject.getString("server_modified"),
entryObject.getLong("size")));
downloadItems.add(downloadItem);
}
return downloadItems;
}
}