package com.orgzly.android.repos; import android.app.Activity; import android.content.Context; import android.net.Uri; import com.dropbox.core.DbxException; import com.dropbox.core.DbxRequestConfig; import com.dropbox.core.android.Auth; import com.dropbox.core.v2.DbxClientV2; import com.dropbox.core.v2.files.FileMetadata; import com.dropbox.core.v2.files.FolderMetadata; import com.dropbox.core.v2.files.GetMetadataErrorException; import com.dropbox.core.v2.files.ListFolderResult; import com.dropbox.core.v2.files.LookupError; import com.dropbox.core.v2.files.Metadata; import com.dropbox.core.v2.files.WriteMode; import com.orgzly.BuildConfig; import com.orgzly.android.BookName; import com.orgzly.android.prefs.AppPreferences; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Locale; public class DropboxClient { private static final String TAG = DropboxClient.class.getName(); private static final long UPLOAD_FILE_SIZE_LIMIT = 150; // MB // TODO: Throw DropboxNotLinked etc. instead and let the client get message from resources private static final String NOT_LINKED = "Not linked to Dropbox"; private static final String LARGE_FILE = "File larger then " + UPLOAD_FILE_SIZE_LIMIT + " MB"; private Context mContext; private DbxClientV2 dbxClient; private boolean tryLinking = false; public DropboxClient(Context context) { mContext = context; String accessToken = loadToken(); if (accessToken != null) { dbxClient = getDbxClient(accessToken); } } public boolean isLinked() { return dbxClient != null; } public void unlink() { dbxClient = null; deleteToken(); tryLinking = false; } public void beginAuthentication(Activity activity) { tryLinking = true; Auth.startOAuth2Authentication(activity, BuildConfig.DROPBOX_APP_KEY); } public boolean finishAuthentication() { if (dbxClient == null && tryLinking) { String accessToken = loadToken(); if (accessToken == null) { accessToken = Auth.getOAuth2Token(); if (accessToken != null) { saveToken(accessToken); } } if (accessToken != null) { dbxClient = getDbxClient(accessToken); return true; } } return false; } private DbxClientV2 getDbxClient(String accessToken) { String userLocale = Locale.getDefault().toString(); String clientId = String.format("%s/%s", BuildConfig.APPLICATION_ID, BuildConfig.VERSION_NAME); DbxRequestConfig requestConfig = DbxRequestConfig .newBuilder(clientId) .withUserLocale(userLocale) .build(); return new DbxClientV2(requestConfig, accessToken); } private void saveToken(String token) { AppPreferences.dropboxToken(mContext, token); } private String loadToken() { return AppPreferences.dropboxToken(mContext); } private void deleteToken() { AppPreferences.dropboxToken(mContext, null); } public List<VersionedRook> getBooks(Uri repoUri) throws IOException { if (! isLinked()) { throw new IOException(NOT_LINKED); } List<VersionedRook> list = new ArrayList<>(); try { String path = repoUri.getPath(); // The empty string ("") represents the root folder in Dropbox API if (path == null || path.equals("/")) { path = ""; } if ("".equals(path) || dbxClient.files().getMetadata(path) instanceof FolderMetadata) { /* Get folder content. */ ListFolderResult result = dbxClient.files().listFolder(path); while (true) { for (Metadata metadata : result.getEntries()) { if (metadata instanceof FileMetadata) { FileMetadata file = (FileMetadata) metadata; if (BookName.isSupportedFormatFileName(file.getName())) { Uri uri = repoUri.buildUpon().appendPath(file.getName()).build(); VersionedRook book = new VersionedRook( repoUri, uri, file.getRev(), file.getServerModified().getTime()); list.add(book); } } } if (!result.getHasMore()) { break; } result = dbxClient.files().listFolderContinue(result.getCursor()); } } else { throw new IOException("Not a directory: " + repoUri); } } catch (DbxException e) { e.printStackTrace(); /* If we get NOT_FOUND from Dropbox, just return the empty list. */ if (e instanceof GetMetadataErrorException) { if (((GetMetadataErrorException) e).errorValue.getPathValue() == LookupError.NOT_FOUND) { return list; } } if (e.getMessage() != null) { throw new IOException("Failed getting the list of files in " + repoUri + ": " + e.getMessage()); } else { throw new IOException("Failed getting the list of files in " + repoUri + ": " + e.toString()); } } return list; } /** * Download file from Dropbox and store it to a local file. */ public VersionedRook download(Uri repoUri, Uri uri, File localFile) throws IOException { if (! isLinked()) { throw new IOException(NOT_LINKED); } OutputStream out = new BufferedOutputStream(new FileOutputStream(localFile)); try { Metadata pathMetadata = dbxClient.files().getMetadata(uri.getPath()); if (pathMetadata instanceof FileMetadata) { FileMetadata metadata = (FileMetadata) pathMetadata; String rev = metadata.getRev(); long mtime = metadata.getServerModified().getTime(); dbxClient.files().download(metadata.getPathLower(), rev).download(out); return new VersionedRook(repoUri, uri, rev, mtime); } else { throw new IOException("Failed downloading Dropbox file " + uri + ": Not a file"); } } catch (DbxException e) { if (e.getMessage() != null) { throw new IOException("Failed downloading Dropbox file " + uri + ": " + e.getMessage()); } else { throw new IOException("Failed downloading Dropbox file " + uri + ": " + e.toString()); } } finally { out.close(); } } /** Upload file to Dropbox. */ public VersionedRook upload(File file, Uri repoUri, String fileName) throws IOException { Uri bookUri = repoUri.buildUpon().appendPath(fileName).build(); if (! isLinked()) { throw new IOException(NOT_LINKED); } if (file.length() > UPLOAD_FILE_SIZE_LIMIT * 1024 * 1024) { throw new IOException(LARGE_FILE); } FileMetadata metadata; InputStream in = new FileInputStream(file); try { metadata = dbxClient.files() .uploadBuilder(bookUri.getPath()) .withMode(WriteMode.OVERWRITE) .uploadAndFinish(in); } catch (DbxException e) { if (e.getMessage() != null) { throw new IOException("Failed overwriting " + bookUri.getPath() + " on Dropbox: " + e.getMessage()); } else { throw new IOException("Failed overwriting " + bookUri.getPath() + " on Dropbox: " + e.toString()); } } String rev = metadata.getRev(); long mtime = metadata.getServerModified().getTime(); return new VersionedRook(repoUri, bookUri, rev, mtime); } public void delete(String path) throws IOException { try { dbxClient.files().delete(path); } catch (DbxException e) { e.printStackTrace(); if (e.getMessage() != null) { throw new IOException("Failed deleting " + path + " on Dropbox: " + e.getMessage()); } else { throw new IOException("Failed deleting " + path + " on Dropbox: " + e.toString()); } } } public VersionedRook move(Uri repoUri, Uri from, Uri to) throws IOException { try { FileMetadata metadata = (FileMetadata) dbxClient.files().move(from.getPath(), to.getPath()); String rev = metadata.getRev(); long mtime = metadata.getServerModified().getTime(); return new VersionedRook(repoUri, to, rev, mtime); } catch (Exception e) { e.printStackTrace(); if (e.getMessage() != null) { // TODO: Move this throwing to utils throw new IOException("Failed moving " + from + " to " + to + ": " + e.getMessage(), e); } else { throw new IOException("Failed moving " + from + " to " + to + ": " + e.toString(), e); } } } }