diff --git a/app/src/main/java/com/orgzly/android/repos/GoogleDriveClient.java b/app/src/main/java/com/orgzly/android/repos/GoogleDriveClient.java index 544af1e0f..afa4674e4 100644 --- a/app/src/main/java/com/orgzly/android/repos/GoogleDriveClient.java +++ b/app/src/main/java/com/orgzly/android/repos/GoogleDriveClient.java @@ -9,24 +9,28 @@ import com.google.api.client.extensions.android.http.AndroidHttp; import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; +import com.google.api.client.http.FileContent; import com.google.api.client.json.gson.GsonFactory; import com.google.api.services.drive.Drive; import com.google.api.services.drive.DriveScopes; +import com.google.api.services.drive.model.File; +import com.google.api.services.drive.model.FileList; import com.orgzly.android.BookName; -import java.util.Collections; - import java.io.BufferedOutputStream; -import java.io.File; +// 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.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import android.util.Log; @@ -39,13 +43,20 @@ public class GoogleDriveClient { private static final String NOT_LINKED = "Not linked to Google Drive"; private static final String LARGE_FILE = "File larger then " + UPLOAD_FILE_SIZE_LIMIT + " MB"; - /* The empty string ("") represents the root folder in Google Drive API v2. */ + /* Using the empty string ("") to represent the root folder. */ private static final String ROOT_PATH = ""; private final Context mContext; private final long repoId; private Drive mDriveService; + private Map pathIds; + { + pathIds = new HashMap<>(); + pathIds.put("My Drive", "root"); + pathIds.put("", "root"); + } + public GoogleDriveClient(Context context, long id) { mContext = context; @@ -68,202 +79,273 @@ private void linkedOrThrow() throws IOException { } } + private String findId(String path) throws IOException { + if (pathIds.containsKey(path)) { + return pathIds.get(path); + } + + String[] parts = path.split("/"); + String[] ids = new String[parts.length+1]; + + ids[0] = "root"; + + for (int i=0; i < parts.length; ++i) { + FileList result = mDriveService.files().list() + .setQ(String.format("name = '%s' and '%s' in parents", parts[i], ids[i])) + .setSpaces("drive") + .setFields("files(id, name, mimeType)") + .execute(); + List files = result.getFiles(); + if (!files.isEmpty()) { + File file = (File) files.get(0); + ids[i+1] = file.getId(); + } + } + + for (int i = 0; i < ids.length; ++i) { + if (ids[i] == null) { + break; + } + pathIds.put(path, ids[i]); + } + + return ids[ids.length-1]; // Returns null if no file is found + } + public List getBooks(Uri repoUri) throws IOException { - return null; - // linkedOrThrow(); - // - // List list = new ArrayList<>(); - // - // String path = repoUri.getPath(); - // - // /* Fix root path. */ - // if (path == null || path.equals("/")) { - // path = ROOT_PATH; - // } - // - // /* Strip trailing slashes. */ - // path = path.replaceAll("/+$", ""); - // - // try { - // // TODO: Alter to fit for google - // if (ROOT_PATH.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( - // repoId, - // RepoType.DROPBOX, - // 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; - // } - // } - // - // throw new IOException("Failed getting the list of files in " + repoUri + - // " listing " + path + ": " + - // (e.getMessage() != null ? e.getMessage() : e.toString())); - // } - // - // return list; + linkedOrThrow(); + + List list = new ArrayList<>(); + + String path = repoUri.getPath(); + + /* Fix root path. */ + if (path == null || path.equals("/")) { + path = ROOT_PATH; + } + + /* Strip trailing slashes. */ + path = path.replaceAll("/+$", ""); + + try { + + String folderId = findId(path); + + + if (folderId != null) { + + File folder = mDriveService.files().get(folderId) + .setFields("id, mimeType") + .execute(); + + if (folder.getMimeType() == "application/vnd.google-apps.folder") { + + String pageToken = null; + do { + FileList result = mDriveService.files().list() + .setQ(String.format("mimeType != 'application/vnd.google-apps.folder' " + + "and '%s' in parents and trashed = false", folderId)) + .setSpaces("drive") + .setFields("nextPageToken, files(id, name, mimeType)") + .setPageToken(pageToken) + .execute(); + for (File file : result.getFiles()) { + if(BookName.isSupportedFormatFileName(file.getName())) { + Uri uri = repoUri.buildUpon().appendPath(file.getName()).build(); + VersionedRook book = new VersionedRook( + repoId, + RepoType.GOOGLE_DRIVE, + repoUri, + uri, + Long.toString(file.getVersion()), + file.getModifiedTime().getValue()); + + list.add(book); + } + } + pageToken = result.getNextPageToken(); + } while (pageToken != null); + + } else { + throw new IOException("Not a directory: " + repoUri); + } + } else { + throw new IOException("Not a directory: " + repoUri); + } + + } catch (Exception e) { + e.printStackTrace(); + + throw new IOException("Failed getting the list of files in " + repoUri + + " listing " + path + ": " + + (e.getMessage() != null ? e.getMessage() : e.toString())); + } + + return list; } /** - * Download file from Dropbox and store it to a local file. + * Download file from Google Drive and store it to a local file. */ - public VersionedRook download(Uri repoUri, String fileName, File localFile) throws IOException { - return null; - // linkedOrThrow(); - // - // Uri uri = repoUri.buildUpon().appendPath(fileName).build(); - // - // OutputStream out = new BufferedOutputStream(new FileOutputStream(localFile)); - // - // try { - // // TODO: Alter to fit for google - // 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(repoId, RepoType.DROPBOX, 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(); - // } + public VersionedRook download(Uri repoUri, String fileName, java.io.File localFile) throws IOException { + linkedOrThrow(); + + Uri uri = repoUri.buildUpon().appendPath(fileName).build(); + + OutputStream out = new BufferedOutputStream(new FileOutputStream(localFile)); + + try { + + String fileId = findId(uri.getPath()); + + if (fileId != null) { + File file = mDriveService.files().get(fileId) + .setFields("id, mimeType, version, modifiedDate") + .execute(); + + if (file.getMimeType() != "application/vnd.google-apps.folder") { + + String rev = Long.toString(file.getVersion()); + long mtime = file.getModifiedTime().getValue(); + + mDriveService.files().get(fileId).executeMediaAndDownloadTo(out); + + return new VersionedRook(repoId, RepoType.GOOGLE_DRIVE, repoUri, uri, rev, mtime); + + } else { + throw new IOException("Failed downloading Google Drive file " + uri + ": Not a file"); + } + } else { + throw new IOException("Failed downloading Google Drive file " + uri + ": File not found"); + } + } catch (Exception e) { + if (e.getMessage() != null) { + throw new IOException("Failed downloading Google Drive file " + uri + ": " + e.getMessage()); + } else { + throw new IOException("Failed downloading Google Drive file " + uri + ": " + e.toString()); + } + } finally { + out.close(); + } } - /** Upload file to Dropbox. */ - public VersionedRook upload(File file, Uri repoUri, String fileName) throws IOException { - return null; - // linkedOrThrow(); - // - // Uri bookUri = repoUri.buildUpon().appendPath(fileName).build(); - // - // if (file.length() > UPLOAD_FILE_SIZE_LIMIT * 1024 * 1024) { - // throw new IOException(LARGE_FILE); - // } - // + /** Upload file to Google Drive. */ + public VersionedRook upload(java.io.File file, Uri repoUri, String fileName) throws IOException { + linkedOrThrow(); + + Uri bookUri = repoUri.buildUpon().appendPath(fileName).build(); + + if (file.length() > UPLOAD_FILE_SIZE_LIMIT * 1024 * 1024) { + throw new IOException(LARGE_FILE); + } + // FileMetadata metadata; // InputStream in = new FileInputStream(file); - // - // try { - // // TODO: Alter to fit for google - // 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(repoId, RepoType.DROPBOX, repoUri, bookUri, rev, mtime); + + File fileMetadata = new File(); + String filePath = bookUri.getPath(); + + try { + fileMetadata.setName(fileName); + fileMetadata.setTrashed(false); + FileContent mediaContent = new FileContent("text/plain", file); + + String fileId = findId(filePath); + + if (fileId == null) { + filePath = "/" + filePath; // Avoids errors when file is in root folder + String folderPath = filePath.substring(0, filePath.lastIndexOf('/')); + String folderId = findId(folderPath); + + fileMetadata.setParents(Collections.singletonList(folderId)); + fileMetadata = mDriveService.files().create(fileMetadata, mediaContent) + .setFields("id, parents") + .execute(); + fileId = fileMetadata.getId(); + + pathIds.put(filePath, fileId); + } else { + fileMetadata = mDriveService.files().update(fileId, fileMetadata, mediaContent) + .setFields("id") + .execute(); + } + + } catch (Exception e) { + if (e.getMessage() != null) { + throw new IOException("Failed overwriting " + filePath + " on Google Drive: " + e.getMessage()); + } else { + throw new IOException("Failed overwriting " + filePath + " on Google Drive: " + e.toString()); + } + } + + String rev = Long.toString(fileMetadata.getVersion()); + long mtime = fileMetadata.getModifiedTime().getValue(); + + return new VersionedRook(repoId, RepoType.GOOGLE_DRIVE, repoUri, bookUri, rev, mtime); } public void delete(String path) throws IOException { - // linkedOrThrow(); - // - // try { - // // TODO: Alter to fit for google - // if (dbxClient.files().getMetadata(path) instanceof FileMetadata) { - // dbxClient.files().deleteV2(path); - // } else { - // throw new IOException("Not a file: " + 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()); - // } - // } + linkedOrThrow(); + + try { + String fileId = findId(path); + + if (fileId != null) { + File file = mDriveService.files().get(fileId).setFields("id, mimeType").execute(); + if (file.getMimeType() != "application/vnd.google-apps.folder") { + File fileMetadata = new File(); + fileMetadata.setTrashed(true); + mDriveService.files().update(fileId, fileMetadata).execute(); + } else { + throw new IOException("Not a file: " + path); + } + } + + } catch (Exception e) { + e.printStackTrace(); + + if (e.getMessage() != null) { + throw new IOException("Failed deleting " + path + " on Google Drive: " + e.getMessage()); + } else { + throw new IOException("Failed deleting " + path + " on Google Drive: " + e.toString()); + } + } } public VersionedRook move(Uri repoUri, Uri from, Uri to) throws IOException { - return null; - // linkedOrThrow(); - // - // try { - // // TODO: Alter to fit for google - // RelocationResult relocationRes = dbxClient.files().moveV2(from.getPath(), to.getPath()); - // Metadata metadata = relocationRes.getMetadata(); - // - // if (! (metadata instanceof FileMetadata)) { - // throw new IOException("Relocated object not a file?"); - // } - // - // FileMetadata fileMetadata = (FileMetadata) metadata; - // - // String rev = fileMetadata.getRev(); - // long mtime = fileMetadata.getServerModified().getTime(); - // - // return new VersionedRook(repoId, RepoType.DROPBOX, 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); - // } - // } + linkedOrThrow(); + + try { + String fileId = findId(from.getPath()); + + File fileMetadata = new File(); + fileMetadata.setName(to.getPath()); + + if (fileId != null) { + fileMetadata = mDriveService.files().update(fileId, fileMetadata) + .setFields("id, mimeType, version, modifiedDate") + .execute(); + + if (fileMetadata.getMimeType() == "application/vnd.google-apps.folder") { + throw new IOException("Relocated object not a file?"); + } + + } + + String rev = Long.toString(fileMetadata.getVersion()); + long mtime = fileMetadata.getModifiedTime().getValue(); + + return new VersionedRook(repoId, RepoType.DROPBOX, 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); + } + } } }