diff --git a/app/build.gradle b/app/build.gradle index bbcd73a2dbd9bc491415c03228ca914ccfa080ef..b7c9e8a5ca2dbedc1a2fe200d08f484b21734e49 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ plugins { def versionMajor = 1 -def versionMinor = 1 +def versionMinor = 2 def versionPatch = 0 diff --git a/app/src/main/java/foundation/e/drive/models/SyncWrapper.java b/app/src/main/java/foundation/e/drive/models/SyncWrapper.java index f6bc93133ed9ee74bcd0d5de499b9d4ae60c676b..9e3becdf022dd4896789c7342a980c49951a1a75 100644 --- a/app/src/main/java/foundation/e/drive/models/SyncWrapper.java +++ b/app/src/main/java/foundation/e/drive/models/SyncWrapper.java @@ -9,11 +9,16 @@ package foundation.e.drive.models; import android.accounts.Account; import android.content.Context; +import android.util.Log; import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.resources.files.ChunkedFileUploadRemoteOperation; + +import java.io.File; import foundation.e.drive.operations.DownloadFileOperation; import foundation.e.drive.operations.UploadFileOperation; +import foundation.e.drive.utils.CommonUtils; /** * This class encapsulates data, for SynchronizationService, about a thread which run a RemoteOperation @@ -50,6 +55,7 @@ public class SyncWrapper { /** * Create the RemoteOperation (to perform file transfer) based on SyncRequest + * @todo: move this method outside of a model object... * @param request syncRequest for the file * @param context App context to be passed to RemoteOperation's contructor * @return RemoteOperation for Upload/Download request or null @@ -59,7 +65,13 @@ public class SyncWrapper { switch (request.getOperationType()) { case UPLOAD: final SyncedFileState sfs = request.getSyncedFileState(); - operation = new UploadFileOperation(sfs, account, context); + final File file = new File(sfs.getLocalPath()); + if (!file.exists()) { + operation = null; + Log.w(SyncWrapper.class.getSimpleName(), "createRemoteOperation: local file doesn't exist for upload"); + } else { + operation = new UploadFileOperation(sfs, account, context); + } break; case DOWNLOAD: final DownloadRequest downloadRequest = (DownloadRequest) request; diff --git a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java index 4cfc10b41ff698d3985a4807a44225304953ea7e..27d10829f31ab5fdc1effc894355ff683e0cf00e 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -20,12 +20,17 @@ import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.UserInfo; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.files.ChunkedFileUploadRemoteOperation; import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation; import com.owncloud.android.lib.resources.files.FileUtils; +import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation; import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation; +import com.owncloud.android.lib.resources.files.model.RemoteFile; import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import java.io.File; +import java.util.ArrayList; + import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.utils.CommonUtils; @@ -74,14 +79,13 @@ public class UploadFileOperation extends RemoteOperation { return new RemoteOperationResult(ResultCode.FORBIDDEN); } - final String targetPath = syncedState.getRemotePath(); - //If file already up-to-date & synced if (syncedState.isLastEtagStored() && syncedState.getLocalLastModified() == file.lastModified()) { Log.d(TAG, "syncedState last modified: "+ syncedState.getLocalLastModified()+" <=> file last modified: "+file.lastModified() +": So return sync_conflict"); return new RemoteOperationResult(ResultCode.SYNC_CONFLICT); } + final NextcloudClient ncClient = DavClientProvider.getInstance().getNcClientInstance(account, context); final RemoteOperationResult checkQuotaResult = checkAvailableSpace(ncClient, file.length()); if (checkQuotaResult.getCode() != ResultCode.OK) { @@ -89,15 +93,33 @@ public class UploadFileOperation extends RemoteOperation { return new RemoteOperationResult(checkQuotaResult.getCode()); } + final String targetPath = syncedState.getRemotePath(); if (!createRemoteFolder(targetPath, client)) { return new RemoteOperationResult(ResultCode.UNKNOWN_ERROR); } - final ResultCode resultCode; boolean mustRestart = true; + final int fileSizeFloorForChunked = 3072000; //3MB + final RemoteOperationResult uploadResult; + + if (file.length() >= fileSizeFloorForChunked) { + Log.d(TAG, "upload "+file.getName()+" as chunked file"); + uploadResult = uploadChunkedFile(file, client); + + if (uploadResult.isSuccess()) { + final RemoteOperationResult result = readRemoteFile(syncedState.getRemotePath(), client); + ArrayList resultData = result.getData(); + if (result.isSuccess() && resultData != null && !resultData.isEmpty()) { + final String latestETag = ((RemoteFile) resultData.get(0)).getEtag(); + uploadResult.setResultData(latestETag); + } + } + + } else { + uploadResult = uploadFile(file, client); + } - final RemoteOperationResult uploadResult = uploadFile(file, client); if (uploadResult.isSuccess()) { final String etag = uploadResult.getResultData(); if (etag != null) { @@ -150,18 +172,6 @@ public class UploadFileOperation extends RemoteOperation { return new RemoteOperationResult(resultCode); } - /** - * Read user info to get Quota's data - * note: this has been extracted from checkAvailableSpace for - * testing purpose - * @param client client to run the method - * @return RemoteOperationResult - */ - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - public RemoteOperationResult readUserInfo(NextcloudClient client) { - final GetUserInfoRemoteOperation GetUserInfoRemoteOperation = new GetUserInfoRemoteOperation(); - return GetUserInfoRemoteOperation.execute(client); - } /** * Effectively upload the file @@ -181,6 +191,43 @@ public class UploadFileOperation extends RemoteOperation { return uploadOperation.execute(client); } + + /** + * Upload a chunked file + * Used for file bigger than 3MB + * @param file + * @param client + * @return + */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public RemoteOperationResult uploadChunkedFile(final File file, final OwnCloudClient client) { + final String mimeType = CommonUtils.getMimeType(file); + final ChunkedFileUploadRemoteOperation uploadOperation = new ChunkedFileUploadRemoteOperation(syncedState.getLocalPath(), + syncedState.getRemotePath(), + mimeType, syncedState.getLastETAG(), + syncedState.getLocalLastModified()+"", false); + return uploadOperation.execute(client); + } + + /** + * Read user info to get Quota's data + * note: this has been extracted from checkAvailableSpace for + * testing purpose + * @param client client to run the method + * @return RemoteOperationResult + */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public RemoteOperationResult readUserInfo(NextcloudClient client) { + final GetUserInfoRemoteOperation GetUserInfoRemoteOperation = new GetUserInfoRemoteOperation(); + return GetUserInfoRemoteOperation.execute(client); + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public RemoteOperationResult readRemoteFile(final String remotePath, final OwnCloudClient client) { + final ReadFileRemoteOperation readRemoteFile = new ReadFileRemoteOperation(remotePath); + return readRemoteFile.execute(client); + } + /** * Create remote parent folder of the file if missing * @param targetPath