diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index 5bb40b30969d..679fb75eac1b 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -264,9 +264,16 @@ class FileUploadHelper { uploadsStorageManager.uploadDao.deleteByRemotePathAndAccountName(remotePath, accountName) } - fun updateUploadStatus(remotePath: String, accountName: String, status: UploadStatus) { + @JvmOverloads + fun updateUploadStatus( + remotePath: String, + accountName: String, + status: UploadStatus, + onCompleted: () -> Unit = {} + ) { ioScope.launch { uploadsStorageManager.uploadDao.updateStatus(remotePath, accountName, status.value) + onCompleted() } } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java index dbd70b68b0f9..b0f1f9e6f0db 100755 --- a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java @@ -317,7 +317,7 @@ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe private class UploadFinishReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - throttler.run("update_upload_list", () -> uploadListAdapter.loadUploadItemsFromDb()); + throttler.run("update_upload_list", () -> uploadListAdapter.loadUploadItemsFromDb(() -> {})); } } } 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 4d8e4164cbdf..09edd56f45f5 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 @@ -58,18 +58,14 @@ import com.owncloud.android.utils.theme.ViewThemeUtils; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.ThreadPoolExecutor; -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. @@ -77,30 +73,25 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter { private static final String TAG = UploadListAdapter.class.getSimpleName(); - private static class GroupConfig { - final Type type; - final int titleRes; - final UploadStatus status; - final NameCollisionPolicy nameCollisionPolicy; - - GroupConfig(Type type, int titleRes, UploadStatus status, NameCollisionPolicy nameCollisionPolicy) { - this.type = type; - this.titleRes = titleRes; - this.status = status; - this.nameCollisionPolicy = nameCollisionPolicy; - } - - public static List getConfigs() { - return List.of( - new GroupConfig(Type.CURRENT, R.string.uploads_view_group_current_uploads, UploadStatus.UPLOAD_IN_PROGRESS, null), - new GroupConfig(Type.FAILED, R.string.uploads_view_group_failed_uploads, UploadStatus.UPLOAD_FAILED, null), - new GroupConfig(Type.CANCELLED, R.string.uploads_view_group_manually_cancelled_uploads, UploadStatus.UPLOAD_CANCELLED, null), - new GroupConfig(Type.FINISHED, R.string.uploads_view_group_finished_uploads, UploadStatus.UPLOAD_SUCCEEDED, NameCollisionPolicy.ASK_USER), // ASK_USER default value - new GroupConfig(Type.SKIPPED, R.string.uploads_view_upload_status_skip, UploadStatus.UPLOAD_SUCCEEDED, NameCollisionPolicy.SKIP) - ); + private record Section( + Type type, + int titleRes, + UploadStatus status, + NameCollisionPolicy collisionPolicy, + OCUpload[] items + ) { + Section withItems(OCUpload[] newItems) { + return new Section(type, titleRes, status, collisionPolicy, newItems); } } + private final List
sections = new ArrayList<>(List.of( + new Section(Type.CURRENT, R.string.uploads_view_group_current_uploads, UploadStatus.UPLOAD_IN_PROGRESS, null, new OCUpload[0]), + new Section(Type.FAILED, R.string.uploads_view_group_failed_uploads, UploadStatus.UPLOAD_FAILED, null, new OCUpload[0]), + new Section(Type.CANCELLED, R.string.uploads_view_group_manually_cancelled_uploads, UploadStatus.UPLOAD_CANCELLED, null, new OCUpload[0]), + new Section(Type.COMPLETED, R.string.uploads_view_group_completed_uploads, UploadStatus.UPLOAD_SUCCEEDED, NameCollisionPolicy.ASK_USER, new OCUpload[0]), + new Section(Type.SKIPPED, R.string.uploads_view_upload_status_skip, UploadStatus.UPLOAD_SUCCEEDED, NameCollisionPolicy.SKIP, new OCUpload[0]))); + private UploadProgressListener uploadProgressListener; private final FileActivity parentActivity; private final UploadsStorageManager uploadsStorageManager; @@ -109,17 +100,9 @@ public static List getConfigs() { 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 final ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, - 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>(1), - new UploadGroupLoadPolicy()); - - private final List uploadGroupConfigs = GroupConfig.getConfigs(); - private final FileUploadHelper uploadHelper = FileUploadHelper.Companion.instance(); public UploadListAdapter(final FileActivity fileActivity, @@ -140,60 +123,30 @@ public UploadListAdapter(final FileActivity fileActivity, this.powerManagementService = powerManagementService; this.clock = clock; this.viewThemeUtils = viewThemeUtils; - - uploadGroups = new UploadGroup[uploadGroupConfigs.size()]; - shouldShowHeadersForEmptySections(false); - initUploadGroups(); showUser = accountManager.getAccounts().length > 1; } - private void initUploadGroups() { - final var optionalUser = parentActivity.getUser(); - if (optionalUser.isEmpty()) { - return; - } - - final var accountName = optionalUser.get().getAccountName(); - - for (int i = 0; i < uploadGroupConfigs.size(); i++) { - final var config = uploadGroupConfigs.get(i); - uploadGroups[i] = createUploadGroup(config, accountName); - } - } - - private UploadGroup createUploadGroup(GroupConfig config, String accountName) { - return new UploadGroup(config.type, parentActivity.getString(config.titleRes)) { - @Override - public void refresh(LoadCompleteListener listener) { - uploadHelper.getUploadsByStatus(accountName, config.status, config.nameCollisionPolicy,ocUploads -> { - fixAndSortItems(ocUploads); - listener.onComplete(); - return Unit.INSTANCE; - }); - } - }; - } - @Override public int getSectionCount() { - return uploadGroups.length; + return sections.size(); } @Override public int getItemCount(int section) { - return uploadGroups[section].getItems().length; + return sections.get(section).items().length; } @Override public void onBindHeaderViewHolder(SectionedViewHolder holder, int section, boolean expanded) { HeaderViewHolder headerViewHolder = (HeaderViewHolder) holder; - UploadGroup group = uploadGroups[section]; + Section group = sections.get(section); + String title = parentActivity.getString(group.titleRes()); + int count = group.items().length; headerViewHolder.binding.uploadListTitle.setText( - String.format(parentActivity.getString(R.string.uploads_view_group_header), - group.getGroupName(), group.getGroupItemCount())); + String.format(parentActivity.getString(R.string.uploads_view_group_header), title, count)); viewThemeUtils.platform.colorPrimaryTextViewElement(headerViewHolder.binding.uploadListTitle); headerViewHolder.binding.uploadListTitle.setOnClickListener(v -> { @@ -211,7 +164,7 @@ public void onBindHeaderViewHolder(SectionedViewHolder holder, int section, bool }}); switch (group.type) { - case CURRENT, FINISHED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close); + case CURRENT, COMPLETED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close); case CANCELLED, FAILED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_dots_vertical); @@ -223,25 +176,37 @@ public void onBindHeaderViewHolder(SectionedViewHolder holder, int section, bool headerViewHolder.binding.uploadListAction.setOnClickListener(v -> { switch (group.type) { case CURRENT -> { - OCUpload ocUpload = group.getItem(0); - if (ocUpload == null) { - return; - } - - String accountName = ocUpload.getAccountName(); - if (accountName == null) { - return; + String accountName = group.items()[0].getAccountName(); + + final int totalUploads = group.items().length; + final int[] completedCount = {0}; + + for (int i=0; i() { + @Override + public Unit invoke() { + FileUploadWorker.Companion.cancelCurrentUpload(upload.getRemotePath(), accountName, new Function0() { + @Override + public Unit invoke() { + completedCount[0]++; + if (completedCount[0] == totalUploads) { + Log_OC.d(TAG, "refreshing upload items"); + + // All uploads finished, refresh UI once + loadUploadItemsFromDb(() -> {}); + } + return Unit.INSTANCE; + } + }); + return Unit.INSTANCE; + } + }); } - - for (OCUpload upload: group.items) { - uploadHelper.updateUploadStatus(upload.getRemotePath(), accountName, UploadStatus.UPLOAD_CANCELLED); - FileUploadWorker.Companion.cancelCurrentUpload(upload.getRemotePath(), accountName, () -> Unit.INSTANCE); - } - loadUploadItemsFromDb(); } - case FINISHED -> { + case COMPLETED -> { uploadsStorageManager.clearSuccessfulUploads(); - loadUploadItemsFromDb(); + loadUploadItemsFromDb(() -> {}); } case FAILED -> showFailedPopupMenu(headerViewHolder); case CANCELLED -> showCancelledPopupMenu(headerViewHolder); @@ -261,14 +226,13 @@ private void showFailedPopupMenu(HeaderViewHolder headerViewHolder) { if (itemId == R.id.action_upload_list_failed_clear) { uploadsStorageManager.clearFailedButNotDelayedUploads(); clearTempEncryptedFolder(); - loadUploadItemsFromDb(); + loadUploadItemsFromDb(() -> {}); } else if (itemId == R.id.action_upload_list_failed_retry) { uploadHelper.retryFailedUploads( uploadsStorageManager, connectivityService, accountManager, powerManagementService); - loadUploadItemsFromDb(); } return true; @@ -286,7 +250,7 @@ private void showCancelledPopupMenu(HeaderViewHolder headerViewHolder) { if (itemId == R.id.action_upload_list_cancelled_clear) { uploadsStorageManager.clearCancelledUploadsForCurrentAccount(); - loadUploadItemsFromDb(); + loadUploadItemsFromDb(() -> {}); clearTempEncryptedFolder(); } else if (itemId == R.id.action_upload_list_cancelled_resume) { retryCancelledUploads(); @@ -311,7 +275,6 @@ private void retryCancelledUploads() { connectivityService, accountManager, powerManagementService); - loadUploadItemsFromDb(); parentActivity.runOnUiThread(() -> { if (showNotExistMessage) { showNotExistMessage(); @@ -331,16 +294,13 @@ public void onBindFooterViewHolder(SectionedViewHolder holder, int section) { @Override public void onBindViewHolder(SectionedViewHolder holder, int section, int relativePosition, int absolutePosition) { - if (uploadGroups.length == 0 || section < 0 || section >= uploadGroups.length) { + if (sections.isEmpty() || section < 0 || section >= sections.size()) { return; } - UploadGroup uploadGroup = uploadGroups[section]; - if (uploadGroup == null) { - return; - } + Section sectionData = sections.get(section); + OCUpload item = sectionData.items()[relativePosition]; - OCUpload item = uploadGroup.getItem(relativePosition); if (item == null) { return; } @@ -467,19 +427,20 @@ public void onBindViewHolder(SectionedViewHolder holder, int section, int relati itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_action_cancel_grey); itemViewHolder.binding.uploadRightButton.setVisibility(View.VISIBLE); itemViewHolder.binding.uploadRightButton.setOnClickListener(v -> { - uploadHelper.updateUploadStatus(item.getRemotePath(), item.getAccountName(), UploadStatus.UPLOAD_CANCELLED); - FileUploadWorker.Companion.cancelCurrentUpload(item.getRemotePath(), item.getAccountName(), () -> Unit.INSTANCE); - loadUploadItemsFromDb(); + uploadHelper.updateUploadStatus(item.getRemotePath(), item.getAccountName(), UploadStatus.UPLOAD_CANCELLED, () -> { + FileUploadWorker.Companion.cancelCurrentUpload(item.getRemotePath(), item.getAccountName(), () -> { + loadUploadItemsFromDb(() -> {}); + return Unit.INSTANCE; + }); + return Unit.INSTANCE; + }); }); } else if (item.getUploadStatus() == UploadStatus.UPLOAD_FAILED) { if (item.getLastResult() == UploadResult.SYNC_CONFLICT) { itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_dots_vertical); itemViewHolder.binding.uploadRightButton.setOnClickListener(view -> { - if (optionalUser.isPresent()) { - User user = optionalUser.get(); - showItemConflictPopup(user, itemViewHolder, item, status, view); - } + optionalUser.ifPresent(user -> showItemConflictPopup(user, itemViewHolder, item, status, view)); }); } else { // Delete @@ -519,12 +480,8 @@ public void onBindViewHolder(SectionedViewHolder holder, int section, int relati Optional user = accountManager.getUser(item.getAccountName()); if (file.exists() && user.isPresent()) { uploadHelper.retryUpload(item, user.get()); - loadUploadItemsFromDb(); } else { - DisplayUtils.showSnackMessage( - v.getRootView().findViewById(android.R.id.content), - R.string.local_file_not_found_message - ); + DisplayUtils.showSnackMessage(v.getRootView().findViewById(android.R.id.content), R.string.local_file_not_found_message); } }); } else if (item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) { @@ -723,7 +680,7 @@ private void showItemConflictPopup(User user, public void removeUpload(OCUpload item) { uploadsStorageManager.removeUpload(item); cancelOldErrorNotification(item); - loadUploadItemsFromDb(); + loadUploadItemsFromDb(() -> {}); } private void refreshFolder( @@ -871,21 +828,31 @@ public SectionedViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int vie } } - /** - * Load upload items from {@link UploadsStorageManager}. - */ - public final void loadUploadItemsFromDb() { - loadUploadItemsFromDb(null); - } + @SuppressLint("NotifyDataSetChanged") + public final void loadUploadItemsFromDb(Runnable onCompleted) { + parentActivity.getUser().ifPresent(user -> { + String accountName = user.getAccountName(); - /** - * Load upload items from {@link UploadsStorageManager}. - * @param loadCompleteListener load complete listener - */ - public final void loadUploadItemsFromDb(LoadCompleteListener loadCompleteListener) { - Log_OC.d(TAG, "loadUploadItemsFromDb"); - // Use thread pool to avoid repeated loading of data - executor.execute(new UploadGroupLoadRunnable(loadCompleteListener)); + for (int i = 0; i < sections.size(); i++) { + final int index = i; + Section sec = sections.get(index); + + uploadHelper.getUploadsByStatus(accountName, sec.status(), sec.collisionPolicy(), uploads -> { + for (OCUpload upload : uploads) { + upload.setDataFixed(uploadHelper); + } + Arrays.sort(uploads, new OCUploadComparator()); + + sections.set(index, sec.withItems(uploads)); + + parentActivity.runOnUiThread(() -> { + notifyDataSetChanged(); + onCompleted.run(); + }); + return Unit.INSTANCE; + }); + } + }); } /** @@ -962,130 +929,8 @@ static class ItemViewHolder extends SectionedViewHolder { } } - interface Refresh { - void refresh(LoadCompleteListener listener); - } - - interface Apply { - void apply(); - } - enum Type { - CURRENT, FINISHED, FAILED, CANCELLED, SKIPPED - } - - abstract class UploadGroup implements Refresh, Apply { - private final Type type; - private OCUpload[] items; - private OCUpload[] refreshItems; - private final String name; - - UploadGroup(Type type, String groupName) { - this.type = type; - this.name = groupName; - items = new OCUpload[0]; - } - - private String getGroupName() { - return name; - } - - public OCUpload[] getItems() { - return items; - } - - public OCUpload getItem(int position) { - if (items.length == 0 || position < 0 || position >= items.length) { - return null; - } - - return items[position]; - } - - public void setItems(OCUpload... items) { - this.items = items; - } - - public void setRefreshItems(OCUpload... items) { - this.refreshItems = items; - } - - void fixAndSortItems(OCUpload... array) { - for (OCUpload upload : array) { - upload.setDataFixed(uploadHelper); - } - Arrays.sort(array, new OCUploadComparator()); - setRefreshItems(array); - } - - private int getGroupItemCount() { - return items == null ? 0 : items.length; - } - - @Override - public void apply() { - setItems(this.refreshItems); - } - } - - public interface LoadCompleteListener { - void onComplete(); - } - - private class UploadGroupLoadRunnable implements Runnable { - - private final Set loadCompleteListenerSet = new HashSet<>(); - - public UploadGroupLoadRunnable(LoadCompleteListener loadCompleteListener) { - if (loadCompleteListener != null) { - loadCompleteListenerSet.add(loadCompleteListener); - } - } - - @SuppressLint("NotifyDataSetChanged") - @Override - public void run() { - final int groupCount = uploadGroups.length; - final int[] completedCount = {0}; - - for (UploadGroup group : uploadGroups) { - group.refresh(() -> { - synchronized (completedCount) { - group.apply(); - completedCount[0]++; - if (completedCount[0] == groupCount) { - - // All groups finished, update UI once - parentActivity.runOnUiThread(() -> { - notifyDataSetChanged(); - for (LoadCompleteListener loadCompleteListener : loadCompleteListenerSet) { - loadCompleteListener.onComplete(); - } - }); - } - } - }); - } - } - } - - private static class UploadGroupLoadPolicy implements RejectedExecutionHandler { - @Override - public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) { - if (executor.isShutdown()) { - return; - } - - Runnable oldest = executor.getQueue().poll(); - - if (oldest instanceof UploadGroupLoadRunnable oldUploadTask && runnable instanceof UploadGroupLoadRunnable newUploadTask) { - if (!oldUploadTask.loadCompleteListenerSet.isEmpty()) { - newUploadTask.loadCompleteListenerSet.addAll(oldUploadTask.loadCompleteListenerSet); - } - } - - executor.execute(runnable); - } + CURRENT, COMPLETED, FAILED, CANCELLED, SKIPPED } public void cancelOldErrorNotification(OCUpload upload) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3912bf80aca3..02355b4c69f4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -289,7 +289,7 @@ Current Failed/pending restart Cancelled - Uploaded + Completed Completed Skipped A file with the same name already exists.