Loading app/build.gradle +2 −2 Original line number Diff line number Diff line Loading @@ -4,8 +4,8 @@ plugins { def versionMajor = 1 def versionMinor = 1 def versionPatch = 1 def versionMinor = 2 def versionPatch = 0 Loading app/src/main/java/foundation/e/drive/models/SyncWrapper.java +10 −0 Original line number Diff line number Diff line Loading @@ -9,9 +9,12 @@ 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 java.io.File; import foundation.e.drive.operations.DownloadFileOperation; import foundation.e.drive.operations.UploadFileOperation; Loading @@ -21,6 +24,7 @@ import foundation.e.drive.operations.UploadFileOperation; * @author Vincent Bourgmayer */ public class SyncWrapper { private final static String TAG = SyncWrapper.class.getSimpleName(); private final SyncRequest request; private final RemoteOperation remoteOperation; private boolean isRunning; Loading Loading @@ -59,6 +63,12 @@ public class SyncWrapper { switch (request.getOperationType()) { case UPLOAD: final SyncedFileState sfs = request.getSyncedFileState(); final File file = new File(sfs.getLocalPath()); if (!file.exists()) { operation = null; Log.w(TAG, "createRemoteOperation: local file doesn't exist for upload"); break; } operation = new UploadFileOperation(sfs, account, context); break; case DOWNLOAD: Loading app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +125 −49 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -37,12 +42,10 @@ import foundation.e.drive.utils.DavClientProvider; */ public class UploadFileOperation extends RemoteOperation { private final static String TAG = UploadFileOperation.class.getSimpleName(); private int restartCounter =0; private long previousLastModified; //get to restore real value if all trials fails private final static int FILE_SIZE_FLOOR_FOR_CHUNKED = 3072000; //3MB private final Context context; private final SyncedFileState syncedState; private final Account account; // /!\ this is temporary because NC library doesn't use NextcloudClient for every operation yet private final Account account; // TODO Remove as soon as nextcloud library move all Operation to NextcloudClient instead of OwncloudClient /** * Construct an upload operation with an already known syncedFileState Loading @@ -50,7 +53,6 @@ public class UploadFileOperation extends RemoteOperation { */ public UploadFileOperation (final SyncedFileState syncedFileState, final Account account, final Context context) { this.syncedState = syncedFileState; this.previousLastModified = syncedState.getLocalLastModified(); this.context = context; this.account = account; } Loading @@ -69,68 +71,111 @@ public class UploadFileOperation extends RemoteOperation { @Override protected RemoteOperationResult run(OwnCloudClient client ) { final File file = new File(syncedState.getLocalPath()); final ResultCode conditionCheckResult = checkCondition(file, client); if (conditionCheckResult != ResultCode.OK) { return new RemoteOperationResult(conditionCheckResult); } final RemoteOperationResult<String> uploadResult; if (file.length() >= FILE_SIZE_FLOOR_FOR_CHUNKED) { Log.d(TAG, "upload " + file.getName() + " as chunked file"); uploadResult = uploadChunkedFile(file, client); } else { uploadResult = uploadFile(file, client); } final ResultCode resultCode; if (uploadResult.isSuccess()) { updateSyncedFileState(uploadResult, file.lastModified(), client); resultCode = uploadResult.getCode(); } else { resultCode = onUploadFailure(uploadResult.getCode(), file.getName()); } DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context); return new RemoteOperationResult(resultCode); } /** * Handle upload's failure * @param uploadResult * @param fileName * @return */ private ResultCode onUploadFailure(final ResultCode uploadResult, final String fileName) { if (uploadResult != ResultCode.CONFLICT && uploadResult != ResultCode.QUOTA_EXCEEDED) { Log.e(TAG, "UploadFileRemoteOperation for : " + fileName + " failed => code: " + uploadResult); return ResultCode.UNKNOWN_ERROR; } return uploadResult; } /** * Check condition required to upload the file: * - the local file exist * - the local file is not already up to date with the remote one * - there is enough free storage * - the remote directory exists * @param file file to upload * @param client client used to execute some request * @return ResultCode.OK if everything is alright */ private ResultCode checkCondition(final File file, final OwnCloudClient client) { if (file == null || !file.exists()) { Log.w(TAG, "Can't get the file. It might have been deleted"); return new RemoteOperationResult(ResultCode.FORBIDDEN); return 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); return ResultCode.SYNC_CONFLICT; } final NextcloudClient ncClient = DavClientProvider.getInstance().getNcClientInstance(account, context); final RemoteOperationResult checkQuotaResult = checkAvailableSpace(ncClient, file.length()); if (checkQuotaResult.getCode() != ResultCode.OK) { Log.e(TAG, "Impossible to check quota. Upload of " + syncedState.getLocalPath() + "cancelled"); return new RemoteOperationResult(checkQuotaResult.getCode()); return checkQuotaResult.getCode(); } final String targetPath = syncedState.getRemotePath(); if (!createRemoteFolder(targetPath, client)) { return new RemoteOperationResult(ResultCode.UNKNOWN_ERROR); return ResultCode.UNKNOWN_ERROR; } return ResultCode.OK; } final ResultCode resultCode; boolean mustRestart = true; final RemoteOperationResult<String> uploadResult = uploadFile(file, client); if (uploadResult.isSuccess()) { final String etag = uploadResult.getResultData(); if (etag != null) { syncedState.setLastETAG(etag); } syncedState.setLocalLastModified(file.lastModified()); resultCode = uploadResult.getCode(); mustRestart = false; } else { if (uploadResult.getCode() == ResultCode.CONFLICT ) { resultCode = ResultCode.CONFLICT; Log.d(TAG, "Catched a conflict result for : "+ file.getName()); mustRestart = false; } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { resultCode = ResultCode.QUOTA_EXCEEDED; mustRestart = false; } else { Log.e(TAG, "UploadFileRemoteOperation for : " + file.getName() + " failed => code: " + uploadResult.getCode()); resultCode = ResultCode.UNKNOWN_ERROR; mustRestart = false; /** * Update syncedFileState (etag & last modified) in case of successful upload * @param uploadResult The Upload's result instance * @param fileLastModified value of local file's last modified * @param client The client used to check etag if missing */ private void updateSyncedFileState(final RemoteOperationResult<String> uploadResult, final long fileLastModified, final OwnCloudClient client) { //The below if statement should only be called for chunked upload. But //for some unknown reason, the simple file upload doesn't give the etag in the result // so, I moved the code here as a security if (uploadResult.getResultData() == null) { final RemoteOperationResult result = readRemoteFile(syncedState.getRemotePath(), client); final ArrayList<Object> resultData = result.getData(); if (result.isSuccess() && resultData != null && !resultData.isEmpty()) { final String latestETag = ((RemoteFile) resultData.get(0)).getEtag(); uploadResult.setResultData(latestETag); } } final String etag = uploadResult.getResultData(); if (mustRestart) { if (this.restartCounter < 1) { this.restartCounter += 1; return this.run(client); } else { syncedState.setLocalLastModified(this.previousLastModified); //Revert syncFileState to its previous state } if (etag != null) { syncedState.setLastETAG(etag); } DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context); return new RemoteOperationResult(resultCode); syncedState.setLocalLastModified(fileLastModified); } /** Loading Loading @@ -164,11 +209,41 @@ public class UploadFileOperation extends RemoteOperation { } /** * Effectively upload the file * Read remote file after upload to retrieve eTag * @param remotePath file's remote path * @param client Owncloudclient instance. @TODO will be replaced by NextcloudClient in future. * @return RemoteOperationResult instance containing failure details or RemoteFile instance */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) public RemoteOperationResult readRemoteFile(final String remotePath, final OwnCloudClient client) { final ReadFileRemoteOperation readRemoteFile = new ReadFileRemoteOperation(remotePath); return readRemoteFile.execute(client); } /** * Upload a chunked file * Used for file bigger than 3MB * @param file File to upload * @param client OwncloudClient to perform the upload. @TODO will be replaced by NextcloudClient in future. * @return RemoteOperationResult instance containing success or failure status with details */ @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); } /** * Upload a file * note: this has been extracted from run(...) for * testing purpose * @param client client to run the method * @return RemoteOperationResult * @param client client to run the method. @TODO will be replaced by NextcloudClient in future. * @return RemoteOperationResult the instance must contains etag in resultData if successful. */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) public RemoteOperationResult<String> uploadFile(final File file, final OwnCloudClient client) { Loading @@ -183,10 +258,11 @@ public class UploadFileOperation extends RemoteOperation { /** * Create remote parent folder of the file if missing * @param targetPath * @param client still OwnCloudClient at the moment, but will be Nextcloud client in the futur * @return * @param targetPath Path of remote directory to create or check for existence * @param client Client to perform the request. @TODO will be replaced by NextcloudClient in future. * @return true if the parent directory has been created, false either */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) public boolean createRemoteFolder(String targetPath, OwnCloudClient client) { final String remoteFolderPath = targetPath.substring(0, targetPath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1); final CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation(remoteFolderPath, true); Loading app/src/main/java/foundation/e/drive/services/SynchronizationService.java +1 −1 Original line number Diff line number Diff line Loading @@ -54,7 +54,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation private ConcurrentHashMap<Integer, SyncWrapper> startedSync; //Integer is thread index (1 to workerAmount) private Account account; private final int workerAmount = 4; private final int workerAmount = 2; private Thread[] threadPool; @Deprecated private OwnCloudClient ocClient; Loading Loading
app/build.gradle +2 −2 Original line number Diff line number Diff line Loading @@ -4,8 +4,8 @@ plugins { def versionMajor = 1 def versionMinor = 1 def versionPatch = 1 def versionMinor = 2 def versionPatch = 0 Loading
app/src/main/java/foundation/e/drive/models/SyncWrapper.java +10 −0 Original line number Diff line number Diff line Loading @@ -9,9 +9,12 @@ 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 java.io.File; import foundation.e.drive.operations.DownloadFileOperation; import foundation.e.drive.operations.UploadFileOperation; Loading @@ -21,6 +24,7 @@ import foundation.e.drive.operations.UploadFileOperation; * @author Vincent Bourgmayer */ public class SyncWrapper { private final static String TAG = SyncWrapper.class.getSimpleName(); private final SyncRequest request; private final RemoteOperation remoteOperation; private boolean isRunning; Loading Loading @@ -59,6 +63,12 @@ public class SyncWrapper { switch (request.getOperationType()) { case UPLOAD: final SyncedFileState sfs = request.getSyncedFileState(); final File file = new File(sfs.getLocalPath()); if (!file.exists()) { operation = null; Log.w(TAG, "createRemoteOperation: local file doesn't exist for upload"); break; } operation = new UploadFileOperation(sfs, account, context); break; case DOWNLOAD: Loading
app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +125 −49 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -37,12 +42,10 @@ import foundation.e.drive.utils.DavClientProvider; */ public class UploadFileOperation extends RemoteOperation { private final static String TAG = UploadFileOperation.class.getSimpleName(); private int restartCounter =0; private long previousLastModified; //get to restore real value if all trials fails private final static int FILE_SIZE_FLOOR_FOR_CHUNKED = 3072000; //3MB private final Context context; private final SyncedFileState syncedState; private final Account account; // /!\ this is temporary because NC library doesn't use NextcloudClient for every operation yet private final Account account; // TODO Remove as soon as nextcloud library move all Operation to NextcloudClient instead of OwncloudClient /** * Construct an upload operation with an already known syncedFileState Loading @@ -50,7 +53,6 @@ public class UploadFileOperation extends RemoteOperation { */ public UploadFileOperation (final SyncedFileState syncedFileState, final Account account, final Context context) { this.syncedState = syncedFileState; this.previousLastModified = syncedState.getLocalLastModified(); this.context = context; this.account = account; } Loading @@ -69,68 +71,111 @@ public class UploadFileOperation extends RemoteOperation { @Override protected RemoteOperationResult run(OwnCloudClient client ) { final File file = new File(syncedState.getLocalPath()); final ResultCode conditionCheckResult = checkCondition(file, client); if (conditionCheckResult != ResultCode.OK) { return new RemoteOperationResult(conditionCheckResult); } final RemoteOperationResult<String> uploadResult; if (file.length() >= FILE_SIZE_FLOOR_FOR_CHUNKED) { Log.d(TAG, "upload " + file.getName() + " as chunked file"); uploadResult = uploadChunkedFile(file, client); } else { uploadResult = uploadFile(file, client); } final ResultCode resultCode; if (uploadResult.isSuccess()) { updateSyncedFileState(uploadResult, file.lastModified(), client); resultCode = uploadResult.getCode(); } else { resultCode = onUploadFailure(uploadResult.getCode(), file.getName()); } DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context); return new RemoteOperationResult(resultCode); } /** * Handle upload's failure * @param uploadResult * @param fileName * @return */ private ResultCode onUploadFailure(final ResultCode uploadResult, final String fileName) { if (uploadResult != ResultCode.CONFLICT && uploadResult != ResultCode.QUOTA_EXCEEDED) { Log.e(TAG, "UploadFileRemoteOperation for : " + fileName + " failed => code: " + uploadResult); return ResultCode.UNKNOWN_ERROR; } return uploadResult; } /** * Check condition required to upload the file: * - the local file exist * - the local file is not already up to date with the remote one * - there is enough free storage * - the remote directory exists * @param file file to upload * @param client client used to execute some request * @return ResultCode.OK if everything is alright */ private ResultCode checkCondition(final File file, final OwnCloudClient client) { if (file == null || !file.exists()) { Log.w(TAG, "Can't get the file. It might have been deleted"); return new RemoteOperationResult(ResultCode.FORBIDDEN); return 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); return ResultCode.SYNC_CONFLICT; } final NextcloudClient ncClient = DavClientProvider.getInstance().getNcClientInstance(account, context); final RemoteOperationResult checkQuotaResult = checkAvailableSpace(ncClient, file.length()); if (checkQuotaResult.getCode() != ResultCode.OK) { Log.e(TAG, "Impossible to check quota. Upload of " + syncedState.getLocalPath() + "cancelled"); return new RemoteOperationResult(checkQuotaResult.getCode()); return checkQuotaResult.getCode(); } final String targetPath = syncedState.getRemotePath(); if (!createRemoteFolder(targetPath, client)) { return new RemoteOperationResult(ResultCode.UNKNOWN_ERROR); return ResultCode.UNKNOWN_ERROR; } return ResultCode.OK; } final ResultCode resultCode; boolean mustRestart = true; final RemoteOperationResult<String> uploadResult = uploadFile(file, client); if (uploadResult.isSuccess()) { final String etag = uploadResult.getResultData(); if (etag != null) { syncedState.setLastETAG(etag); } syncedState.setLocalLastModified(file.lastModified()); resultCode = uploadResult.getCode(); mustRestart = false; } else { if (uploadResult.getCode() == ResultCode.CONFLICT ) { resultCode = ResultCode.CONFLICT; Log.d(TAG, "Catched a conflict result for : "+ file.getName()); mustRestart = false; } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { resultCode = ResultCode.QUOTA_EXCEEDED; mustRestart = false; } else { Log.e(TAG, "UploadFileRemoteOperation for : " + file.getName() + " failed => code: " + uploadResult.getCode()); resultCode = ResultCode.UNKNOWN_ERROR; mustRestart = false; /** * Update syncedFileState (etag & last modified) in case of successful upload * @param uploadResult The Upload's result instance * @param fileLastModified value of local file's last modified * @param client The client used to check etag if missing */ private void updateSyncedFileState(final RemoteOperationResult<String> uploadResult, final long fileLastModified, final OwnCloudClient client) { //The below if statement should only be called for chunked upload. But //for some unknown reason, the simple file upload doesn't give the etag in the result // so, I moved the code here as a security if (uploadResult.getResultData() == null) { final RemoteOperationResult result = readRemoteFile(syncedState.getRemotePath(), client); final ArrayList<Object> resultData = result.getData(); if (result.isSuccess() && resultData != null && !resultData.isEmpty()) { final String latestETag = ((RemoteFile) resultData.get(0)).getEtag(); uploadResult.setResultData(latestETag); } } final String etag = uploadResult.getResultData(); if (mustRestart) { if (this.restartCounter < 1) { this.restartCounter += 1; return this.run(client); } else { syncedState.setLocalLastModified(this.previousLastModified); //Revert syncFileState to its previous state } if (etag != null) { syncedState.setLastETAG(etag); } DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context); return new RemoteOperationResult(resultCode); syncedState.setLocalLastModified(fileLastModified); } /** Loading Loading @@ -164,11 +209,41 @@ public class UploadFileOperation extends RemoteOperation { } /** * Effectively upload the file * Read remote file after upload to retrieve eTag * @param remotePath file's remote path * @param client Owncloudclient instance. @TODO will be replaced by NextcloudClient in future. * @return RemoteOperationResult instance containing failure details or RemoteFile instance */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) public RemoteOperationResult readRemoteFile(final String remotePath, final OwnCloudClient client) { final ReadFileRemoteOperation readRemoteFile = new ReadFileRemoteOperation(remotePath); return readRemoteFile.execute(client); } /** * Upload a chunked file * Used for file bigger than 3MB * @param file File to upload * @param client OwncloudClient to perform the upload. @TODO will be replaced by NextcloudClient in future. * @return RemoteOperationResult instance containing success or failure status with details */ @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); } /** * Upload a file * note: this has been extracted from run(...) for * testing purpose * @param client client to run the method * @return RemoteOperationResult * @param client client to run the method. @TODO will be replaced by NextcloudClient in future. * @return RemoteOperationResult the instance must contains etag in resultData if successful. */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) public RemoteOperationResult<String> uploadFile(final File file, final OwnCloudClient client) { Loading @@ -183,10 +258,11 @@ public class UploadFileOperation extends RemoteOperation { /** * Create remote parent folder of the file if missing * @param targetPath * @param client still OwnCloudClient at the moment, but will be Nextcloud client in the futur * @return * @param targetPath Path of remote directory to create or check for existence * @param client Client to perform the request. @TODO will be replaced by NextcloudClient in future. * @return true if the parent directory has been created, false either */ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) public boolean createRemoteFolder(String targetPath, OwnCloudClient client) { final String remoteFolderPath = targetPath.substring(0, targetPath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1); final CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation(remoteFolderPath, true); Loading
app/src/main/java/foundation/e/drive/services/SynchronizationService.java +1 −1 Original line number Diff line number Diff line Loading @@ -54,7 +54,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation private ConcurrentHashMap<Integer, SyncWrapper> startedSync; //Integer is thread index (1 to workerAmount) private Account account; private final int workerAmount = 4; private final int workerAmount = 2; private Thread[] threadPool; @Deprecated private OwnCloudClient ocClient; Loading