瀏覽代碼

Merge pull request #12251 from nextcloud/feature/better-feedback-for-failed-uploads

Better Feedback For Failed Uploads
Alper Öztürk 1 年之前
父節點
當前提交
06f51e1ddd

+ 51 - 24
app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt

@@ -253,6 +253,50 @@ class FilesUploadWorker(
         // TODO generalize for automated uploads
     }
 
+    private fun createConflictResolveAction(context: Context, uploadFileOperation: UploadFileOperation): PendingIntent {
+        val intent = ConflictsResolveActivity.createIntent(
+            uploadFileOperation.file,
+            uploadFileOperation.user,
+            uploadFileOperation.ocUploadId,
+            Intent.FLAG_ACTIVITY_CLEAR_TOP,
+            context
+        )
+
+        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE)
+        } else {
+            PendingIntent.getActivity(
+                context,
+                0,
+                intent,
+                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+            )
+        }
+    }
+
+    private fun addConflictResolveActionToNotification(uploadFileOperation: UploadFileOperation) {
+        val intent: PendingIntent = createConflictResolveAction(context, uploadFileOperation)
+
+        notificationBuilder.addAction(
+            R.drawable.ic_cloud_upload,
+            context.getString(R.string.upload_list_resolve_conflict),
+            intent
+        )
+    }
+
+    private fun addUploadListContentIntent(uploadFileOperation: UploadFileOperation) {
+        val uploadListIntent = createUploadListIntent(uploadFileOperation)
+
+        notificationBuilder.setContentIntent(
+            PendingIntent.getActivity(
+                context,
+                System.currentTimeMillis().toInt(),
+                uploadListIntent,
+                PendingIntent.FLAG_IMMUTABLE
+            )
+        )
+    }
+
     /**
      * adapted from [com.owncloud.android.files.services.FileUploader.notifyUploadResult]
      */
@@ -300,23 +344,16 @@ class FilesUploadWorker(
 
             val content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, uploadFileOperation, context.resources)
 
+            addUploadListContentIntent(uploadFileOperation)
+
+            if (uploadResult.code == ResultCode.SYNC_CONFLICT) {
+                addConflictResolveActionToNotification(uploadFileOperation)
+            }
+
             if (needsToUpdateCredentials) {
                 createUpdateCredentialsNotification(uploadFileOperation.user.toPlatformAccount())
-            } else {
-                val intent = if (uploadResult.code == ResultCode.SYNC_CONFLICT) {
-                    createResolveConflictIntent(uploadFileOperation)
-                } else {
-                    createUploadListIntent(uploadFileOperation)
-                }
-                notificationBuilder.setContentIntent(
-                    PendingIntent.getActivity(
-                        context,
-                        System.currentTimeMillis().toInt(),
-                        intent,
-                        PendingIntent.FLAG_IMMUTABLE
-                    )
-                )
             }
+
             notificationBuilder.setContentText(content)
 
             notificationManager.notify(
@@ -336,16 +373,6 @@ class FilesUploadWorker(
         )
     }
 
-    private fun createResolveConflictIntent(uploadFileOperation: UploadFileOperation): Intent {
-        return ConflictsResolveActivity.createIntent(
-            uploadFileOperation.file,
-            uploadFileOperation.user,
-            uploadFileOperation.ocUploadId,
-            Intent.FLAG_ACTIVITY_CLEAR_TOP,
-            context
-        )
-    }
-
     private fun createUpdateCredentialsNotification(account: Account) {
         // let the user update credentials with one click
         val updateAccountCredentials = Intent(context, AuthenticatorActivity::class.java)

+ 27 - 0
app/src/main/java/com/nextcloud/model/HTTPStatusCodes.kt

@@ -0,0 +1,27 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.model
+
+@Suppress("MagicNumber")
+enum class HTTPStatusCodes(val code: Int) {
+    NOT_FOUND(404)
+}

+ 16 - 4
app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt

@@ -21,6 +21,7 @@ import android.content.Intent
 import android.os.Bundle
 import android.widget.Toast
 import com.nextcloud.client.account.User
+import com.nextcloud.model.HTTPStatusCodes
 import com.nextcloud.utils.extensions.getParcelableArgument
 import com.owncloud.android.R
 import com.owncloud.android.datamodel.OCFile
@@ -164,7 +165,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
                         startDialog()
                     } else {
                         Log_OC.e(TAG, "ReadFileRemoteOp returned failure with code: " + result.httpCode)
-                        showErrorAndFinish()
+                        showErrorAndFinish(result.httpCode)
                     }
                 } catch (e: Exception) {
                     Log_OC.e(TAG, "Error when trying to fetch remote file", e)
@@ -203,9 +204,20 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
         }
     }
 
-    private fun showErrorAndFinish() {
-        runOnUiThread { Toast.makeText(this, R.string.conflict_dialog_error, Toast.LENGTH_LONG).show() }
-        finish()
+    private fun showErrorAndFinish(code: Int? = null) {
+        val message = parseErrorMessage(code)
+        runOnUiThread {
+            Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+            finish()
+        }
+    }
+
+    private fun parseErrorMessage(code: Int?): String {
+        return if (code == HTTPStatusCodes.NOT_FOUND.code) {
+            getString(R.string.uploader_file_not_found_on_server_message)
+        } else {
+            getString(R.string.conflict_dialog_error)
+        }
     }
 
     /**

+ 1 - 0
app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt

@@ -128,6 +128,7 @@ class NotificationsActivity : DrawerActivity(), NotificationsContract.View {
         return
     }
 
+    @Suppress("NestedBlockDepth")
     private fun setupPushWarning() {
         if (!resources.getBoolean(R.bool.show_push_warning)) {
             return

+ 6 - 4
app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java

@@ -57,6 +57,7 @@ import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
 import com.owncloud.android.ui.adapter.UploadListAdapter;
 import com.owncloud.android.ui.decoration.MediaGridItemDecoration;
+import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.FilesSyncHelper;
 import com.owncloud.android.utils.theme.ViewThemeUtils;
 
@@ -201,12 +202,13 @@ public class UploadListActivity extends FileActivity {
             uploadsStorageManager,
             connectivityService,
             userAccountManager,
-            powerManagementService
-                                                        )).start();
+            powerManagementService))
+            .start();
 
         // update UI
         uploadListAdapter.loadUploadItemsFromDb();
         swipeListRefreshLayout.setRefreshing(false);
+        DisplayUtils.showSnackMessage(this, R.string.uploader_local_files_uploaded);
     }
 
     @Override
@@ -335,11 +337,11 @@ public class UploadListActivity extends FileActivity {
                     uploadListAdapter.loadUploadItemsFromDb();
                 } else {
                     Log_OC.d(TAG, "mUploaderBinder already set. mUploaderBinder: " +
-                            mUploaderBinder + " service:" + service);
+                        mUploaderBinder + " service:" + service);
                 }
             } else {
                 Log_OC.d(TAG, "UploadListActivity not connected to Upload service. component: " +
-                        component + " service: " + service);
+                    component + " service: " + service);
             }
         }
 

+ 63 - 65
app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java

@@ -83,17 +83,17 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
     private static final String TAG = UploadListAdapter.class.getSimpleName();
 
     private ProgressListener progressListener;
-    private FileActivity parentActivity;
-    private UploadsStorageManager uploadsStorageManager;
-    private FileDataStorageManager storageManager;
-    private ConnectivityService connectivityService;
-    private PowerManagementService powerManagementService;
-    private UserAccountManager accountManager;
+    private final FileActivity parentActivity;
+    private final UploadsStorageManager uploadsStorageManager;
+    private final FileDataStorageManager storageManager;
+    private final ConnectivityService connectivityService;
+    private final PowerManagementService powerManagementService;
+    private final UserAccountManager accountManager;
+    private final Clock clock;
+    private final UploadGroup[] uploadGroups;
+    private final boolean showUser;
+    private final ViewThemeUtils viewThemeUtils;
     private NotificationManager mNotificationManager;
-    private Clock clock;
-    private UploadGroup[] uploadGroups;
-    private boolean showUser;
-    private final  ViewThemeUtils viewThemeUtils;
 
     @Override
     public int getSectionCount() {
@@ -119,42 +119,31 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         headerViewHolder.binding.uploadListTitle.setOnClickListener(v -> toggleSectionExpanded(section));
 
         switch (group.type) {
-            case CURRENT:
-            case FINISHED:
-                headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close);
-                break;
-            case FAILED:
-                headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_sync);
-                break;
+            case CURRENT, FINISHED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close);
+            case FAILED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_sync);
         }
 
         headerViewHolder.binding.uploadListAction.setOnClickListener(v -> {
             switch (group.type) {
-                case CURRENT:
+                case CURRENT -> {
                     FileUploader.FileUploaderBinder uploaderBinder = parentActivity.getFileUploaderBinder();
-
                     if (uploaderBinder != null) {
                         for (OCUpload upload : group.getItems()) {
                             uploaderBinder.cancel(upload);
                         }
                     }
-                    break;
-                case FINISHED:
-                    uploadsStorageManager.clearSuccessfulUploads();
-                    break;
-                case FAILED:
-                    new Thread(() -> FileUploader.retryFailedUploads(
-                        parentActivity,
-                        uploadsStorageManager,
-                        connectivityService,
-                        accountManager,
-                        powerManagementService
-                                                                    )).start();
-                    break;
-
-                default:
-                    // do nothing
-                    break;
+                }
+                case FINISHED -> uploadsStorageManager.clearSuccessfulUploads();
+                case FAILED -> new Thread(() -> FileUploader.retryFailedUploads(
+                    parentActivity,
+                    uploadsStorageManager,
+                    connectivityService,
+                    accountManager,
+                    powerManagementService
+                                                                               )).start();
+                default -> {
+                }
+                // do nothing
             }
 
             loadUploadItemsFromDb();
@@ -217,6 +206,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         loadUploadItemsFromDb();
     }
 
+
     @Override
     public void onBindViewHolder(SectionedViewHolder holder, int section, int relativePosition, int absolutePosition) {
         ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
@@ -276,11 +266,10 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         // Update information depending of upload details
         String status = getStatusText(item);
         switch (item.getUploadStatus()) {
-            case UPLOAD_IN_PROGRESS:
+            case UPLOAD_IN_PROGRESS -> {
                 viewThemeUtils.platform.themeHorizontalProgressBar(itemViewHolder.binding.uploadProgressBar);
                 itemViewHolder.binding.uploadProgressBar.setProgress(0);
                 itemViewHolder.binding.uploadProgressBar.setVisibility(View.VISIBLE);
-
                 FileUploader.FileUploaderBinder binder = parentActivity.getFileUploaderBinder();
                 if (binder != null) {
                     if (binder.isUploadingNow(item)) {
@@ -290,7 +279,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
                             binder.removeDatatransferProgressListener(
                                 progressListener,
                                 progressListener.getUpload()   // the one that was added
-                            );
+                                                                     );
                         }
                         // ... then, bind the current progress bar to listen for updates
                         progressListener = new ProgressListener(item, itemViewHolder.binding.uploadProgressBar);
@@ -308,19 +297,12 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
                 } else {
                     Log_OC.w(TAG, "FileUploaderBinder not ready yet for upload " + item.getRemotePath());
                 }
-
                 itemViewHolder.binding.uploadDate.setVisibility(View.GONE);
                 itemViewHolder.binding.uploadFileSize.setVisibility(View.GONE);
                 itemViewHolder.binding.uploadProgressBar.invalidate();
-                break;
-
-            case UPLOAD_FAILED:
-                itemViewHolder.binding.uploadDate.setVisibility(View.GONE);
-                break;
-
-            case UPLOAD_SUCCEEDED:
-                itemViewHolder.binding.uploadStatus.setVisibility(View.GONE);
-                break;
+            }
+            case UPLOAD_FAILED -> itemViewHolder.binding.uploadDate.setVisibility(View.GONE);
+            case UPLOAD_SUCCEEDED -> itemViewHolder.binding.uploadStatus.setVisibility(View.GONE);
         }
         itemViewHolder.binding.uploadStatus.setText(status);
 
@@ -507,29 +489,22 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
                                                          OCUpload item,
                                                          String status) {
         String remotePath = item.getRemotePath();
-        OCFile ocFile = storageManager.getFileByPath(remotePath);
+        OCFile localFile = storageManager.getFileByEncryptedRemotePath(remotePath);
 
-        if (ocFile == null) {
+        if (localFile == null) {
             // Remote file doesn't exist, try to refresh folder
-            OCFile folder = storageManager.getFileByPath(new File(remotePath).getParent() + "/");
+            OCFile folder = storageManager.getFileByEncryptedRemotePath(new File(remotePath).getParent() + "/");
+
             if (folder != null && folder.isFolder()) {
-                this.refreshFolder(itemViewHolder, user, folder, (caller, result) -> {
-                    itemViewHolder.binding.uploadStatus.setText(status);
-                    if (result.isSuccess()) {
-                        OCFile file = storageManager.getFileByPath(remotePath);
-                        if (file != null) {
-                            this.openConflictActivity(file, item);
-                        }
-                    }
-                });
+                refreshFolderAndUpdateUI(itemViewHolder, user, folder, remotePath, item, status);
                 return true;
             }
 
             // Destination folder doesn't exist anymore
         }
 
-        if (ocFile != null) {
-            this.openConflictActivity(ocFile, item);
+        if (localFile != null) {
+            this.openConflictActivity(localFile, item);
             return true;
         }
 
@@ -537,6 +512,29 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         return false;
     }
 
+    private void refreshFolderAndUpdateUI(ItemViewHolder holder, User user, OCFile folder, String remotePath, OCUpload item, String status) {
+        Context context = MainApp.getAppContext();
+
+        this.refreshFolder(context, holder, user, folder, (caller, result) -> {
+            holder.binding.uploadStatus.setText(status);
+
+            if (result.isSuccess()) {
+                OCFile fileOnServer = storageManager.getFileByEncryptedRemotePath(remotePath);
+
+                if (fileOnServer != null) {
+                    openConflictActivity(fileOnServer, item);
+                } else {
+                    displayFileNotFoundError(holder.itemView, context);
+                }
+            }
+        });
+    }
+
+    private void displayFileNotFoundError(View itemView, Context context) {
+        String message = context.getString(R.string.uploader_file_not_found_message);
+        DisplayUtils.showSnackMessage(itemView, message);
+    }
+
     private void showItemConflictPopup(User user,
                                        ItemViewHolder itemViewHolder,
                                        OCUpload item,
@@ -558,20 +556,20 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         popup.show();
     }
 
-    private void removeUpload(OCUpload item) {
+    public void removeUpload(OCUpload item) {
         uploadsStorageManager.removeUpload(item);
         cancelOldErrorNotification(item);
         loadUploadItemsFromDb();
     }
 
     private void refreshFolder(
+        Context context,
         ItemViewHolder view,
         User user,
         OCFile folder,
         OnRemoteOperationListener listener) {
         view.binding.uploadListItemLayout.setClickable(false);
         view.binding.uploadStatus.setText(R.string.uploads_view_upload_status_fetching_server_version);
-        Context context = MainApp.getAppContext();
         new RefreshFolderOperation(folder,
                                    clock.getCurrentTime(),
                                    false,

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -913,6 +913,9 @@
     <string name="failed_to_start_editor">Failed to start editor</string>
     <string name="create_rich_workspace">Add folder info</string>
     <string name="creates_rich_workspace">creates folder info</string>
+    <string name="uploader_local_files_uploaded">Try to upload local files again</string>
+    <string name="uploader_file_not_found_on_server_message">We couldnt locate the file on server. Another user may have deleted the file</string>
+    <string name="uploader_file_not_found_message">File not found. Are you sure this file exist or conflict not solved before?</string>
     <string name="uploader_upload_failed_sync_conflict_error">File upload conflict</string>
     <string name="uploader_upload_failed_sync_conflict_error_content">Pick which version to keep of %1$s</string>
     <string name="upload_list_resolve_conflict">Resolve conflict</string>