diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index 65a32ff8cab1..1651977e68bf 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -46,6 +46,8 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.lib.resources.files.Chunk; +import com.owncloud.android.lib.resources.files.ChunkUploadListener; import com.owncloud.android.lib.resources.files.ChunkedFileUploadRemoteOperation; import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation; @@ -90,11 +92,13 @@ import java.security.cert.CertificateException; import java.security.spec.InvalidParameterSpecException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import javax.crypto.BadPaddingException; @@ -103,14 +107,16 @@ import javax.crypto.NoSuchPaddingException; import androidx.annotation.CheckResult; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import kotlin.Pair; import kotlin.Triple; import kotlin.Unit; /** * Operation performing the update in the ownCloud server of a file that was modified locally. */ -public class UploadFileOperation extends SyncOperation { +public class UploadFileOperation extends SyncOperation implements ChunkUploadListener { private static final String TAG = UploadFileOperation.class.getSimpleName(); @@ -746,7 +752,8 @@ private RemoteOperationResult performE2EUpload(E2EClientData data) throws Operat mUploadOperation.addDataTransferProgressListener(mDataTransferListener); } - if (mCancellationRequested.get()) { + if (shouldCancelUpload()) { + processCancellationIfRequested(); throw new OperationCancelledException(); } @@ -1057,6 +1064,11 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { mDisableRetries); } + if (mUploadOperation instanceof ChunkedFileUploadRemoteOperation chunkedFileUploadRemoteOperation) { + chunkedFileUploadRemoteOperation.setChunkUploadListener(this); + } + + /** * Adds the onTransferProgress in FileUploadWorker * {@link FileUploadWorker#onTransferProgress(long, long, long, String)()} @@ -1065,7 +1077,8 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { mUploadOperation.addDataTransferProgressListener(mDataTransferListener); } - if (mCancellationRequested.get()) { + if (shouldCancelUpload()) { + processCancellationIfRequested(); throw new OperationCancelledException(); } @@ -1131,6 +1144,10 @@ private RemoteOperationResult normalUpload(OwnCloudClient client) { return result; } + private boolean shouldCancelUpload() { + return mCancellationRequested.get() || (getMatchingCancellationRequest(mRemotePath, user.getAccountName()) != null); + } + private void updateSize(long size) { OCUpload ocUpload = uploadsStorageManager.getUploadById(getOCUploadId()); if (ocUpload != null) { @@ -1168,7 +1185,8 @@ private RemoteOperationResult copyFile(File originalFile, String expectedPath) t return copy(originalFile, temporalFile); } - if (mCancellationRequested.get()) { + if (shouldCancelUpload()) { + processCancellationIfRequested(); throw new OperationCancelledException(); } @@ -1210,7 +1228,8 @@ private RemoteOperationResult checkNameCollision(OCFile parentFile, } } - if (mCancellationRequested.get()) { + if (shouldCancelUpload()) { + processCancellationIfRequested(); throw new OperationCancelledException(); } @@ -1519,7 +1538,7 @@ private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOEx out = new FileOutputStream(targetFile); int nRead; byte[] buf = new byte[4096]; - while (!mCancellationRequested.get() && + while (!shouldCancelUpload() && (nRead = in.read(buf)) > -1) { out.write(buf, 0, nRead); } @@ -1527,7 +1546,8 @@ private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOEx } // else: weird but possible situation, nothing to copy - if (mCancellationRequested.get()) { + if (shouldCancelUpload()) { + processCancellationIfRequested(); return new RemoteOperationResult(new OperationCancelledException()); } } catch (Exception e) { @@ -1673,4 +1693,56 @@ public interface OnRenameListener { void onRenameUpload(); } + /** Thread-safe set of pending cancellation requests. Each pair represents (remotePath, accountName). */ + private final static Set> pendingCancellationRequests = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + /** + * Adds a cancellation request for a specific upload. + *

+ * This does not immediately cancel the upload. It only marks it for cancellation. + * The upload will be cancelled later when {@link #processCancellationIfRequested()} is called. + * + * @param remotePath the remote path of the file being uploaded + * @param accountName the account name of the user who owns the upload + */ + public static void requestCancellation(String remotePath, String accountName) { + pendingCancellationRequests.add(new Pair<>(remotePath, accountName)); + } + + /** + * Checks if a cancellation has been requested for the current upload file operation, and cancels it if so. + *

+ * If a matching request exists, the upload will be cancelled and the request removed + * from the pending set. + * + * @return true if the upload was cancelled due to a pending request + */ + public boolean processCancellationIfRequested() { + final var cancellationRequest = getMatchingCancellationRequest(mRemotePath, user.getAccountName()); + if (cancellationRequest != null) { + cancel(ResultCode.USER_CANCELLED); + pendingCancellationRequests.remove(cancellationRequest); + return true; + } + + return false; + } + + public static Pair getMatchingCancellationRequest(String remotePath, String accountName) { + for (Pair cancelRequest : pendingCancellationRequests) { + if (remotePath.equals(cancelRequest.getFirst()) && accountName.equals(cancelRequest.getSecond())) { + return cancelRequest; + } + } + return null; + } + + @Override + public boolean isCancelled() { + final var result = shouldCancelUpload(); + if (result) { + processCancellationIfRequested(); + } + return result; + } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java index b06a2b0a5d2d..f1afcb2c2807 100755 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java @@ -46,6 +46,7 @@ import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.operations.RefreshFolderOperation; +import com.owncloud.android.operations.UploadFileOperation; import com.owncloud.android.ui.activity.ConflictsResolveActivity; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; @@ -67,8 +68,6 @@ import java.util.concurrent.TimeUnit; import androidx.annotation.NonNull; -import kotlin.Unit; -import kotlin.jvm.functions.Function0; /** * This Adapter populates a ListView with following types of uploads: pending, active, completed. Filtering possible. @@ -148,7 +147,7 @@ public void onBindHeaderViewHolder(SectionedViewHolder holder, int section, bool for (OCUpload upload: group.items) { uploadHelper.setStatusOfUploadToCancel(upload.getRemotePath()); - FileUploadWorker.Companion.cancelCurrentUpload(upload.getRemotePath(), accountName, () -> Unit.INSTANCE); + UploadFileOperation.requestCancellation(upload.getRemotePath(), accountName); } loadUploadItemsFromDb(); }).start(); @@ -433,7 +432,7 @@ public void onBindViewHolder(SectionedViewHolder holder, int section, int relati itemViewHolder.binding.uploadRightButton.setVisibility(View.VISIBLE); itemViewHolder.binding.uploadRightButton.setOnClickListener(v -> { uploadHelper.setStatusOfUploadToCancel(item.getRemotePath()); - FileUploadWorker.Companion.cancelCurrentUpload(item.getRemotePath(), item.getAccountName(), () -> Unit.INSTANCE); + UploadFileOperation.requestCancellation(item.getRemotePath(), item.getAccountName()); loadUploadItemsFromDb(); }); diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 268abd74aefa..d624de63aa77 100755 --- a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -53,6 +53,7 @@ import com.owncloud.android.lib.resources.shares.ShareType; import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.operations.SynchronizeFileOperation; +import com.owncloud.android.operations.UploadFileOperation; import com.owncloud.android.services.OperationsService; import com.owncloud.android.ui.activity.ConflictsResolveActivity; import com.owncloud.android.ui.activity.ExternalSiteWebView; @@ -1019,6 +1020,7 @@ public void cancelTransference(OCFile file) { final var fileUploadHelper = FileUploadHelper.Companion.instance(); if (fileUploadHelper.isUploading(file.getRemotePath(), currentUser.getAccountName())) { + UploadFileOperation.requestCancellation(file.getRemotePath(), currentUser.getAccountName()); FileUploadWorker.Companion.cancelCurrentUpload(file.getRemotePath(), currentUser.getAccountName(), () -> { fileUploadHelper.setStatusOfUploadToCancel(file.getRemotePath()); return Unit.INSTANCE; diff --git a/build.gradle b/build.gradle index a199fbcfcd95..3630591ad823 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ */ buildscript { ext { - androidLibraryVersion ="3546bd82fc" + androidLibraryVersion ="1dae051f17" androidCommonLibraryVersion = "0.28.0" androidPluginVersion = '8.13.0' androidxMediaVersion = "1.5.1" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index a2be80a32d49..3ad560036138 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -15990,6 +15990,14 @@ + + + + + + + +