diff --git a/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt index f298128bd585..a94b0c9c669c 100644 --- a/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt +++ b/app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt @@ -91,20 +91,27 @@ interface FileDao { @Query( """ - SELECT * - FROM filelist - WHERE parent = :parentId - AND file_owner = :accountName - AND (content_type != :dirType AND content_type != :webdavType) - ORDER BY ${ProviderTableMeta.FILE_DEFAULT_SORT_ORDER} - """ + SELECT + EXISTS ( + SELECT 1 + FROM filelist + WHERE parent = :parentId + AND file_owner = :accountName + AND content_type != '${MimeType.DIRECTORY}' + AND content_type != '${MimeType.WEBDAV_FOLDER}' + ) + AND NOT EXISTS ( + SELECT 1 + FROM filelist + WHERE parent = :parentId + AND file_owner = :accountName + AND content_type != '${MimeType.DIRECTORY}' + AND content_type != '${MimeType.WEBDAV_FOLDER}' + AND media_path IS NULL + ) +""" ) - suspend fun getSubfiles( - parentId: Long, - accountName: String, - dirType: String = MimeType.DIRECTORY, - webdavType: String = MimeType.WEBDAV_FOLDER - ): List + fun isFolderFullyDownloaded(parentId: Long, accountName: String): Boolean @Query( """ diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt index 77f662d76c11..4bb8124f19c6 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt @@ -44,11 +44,6 @@ fun FileDataStorageManager.getDecryptedPath(file: OCFile): String { .joinToString(OCFile.PATH_SEPARATOR) } -suspend fun FileDataStorageManager.getSubfiles(id: Long, accountName: String): List = - fileDao.getSubfiles(id, accountName).map { - createFileInstance(it) - } - fun FileDataStorageManager.getNonEncryptedSubfolders(id: Long, accountName: String): List = fileDao.getNonEncryptedSubfolders(id, accountName).map { createFileInstance(it) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt index 1108e7572f65..ecf762b5fdb4 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt @@ -15,30 +15,6 @@ import com.owncloud.android.datamodel.OCFileDepth.FirstLevel import com.owncloud.android.datamodel.OCFileDepth.Root import com.owncloud.android.utils.FileStorageUtils -@Suppress("ReturnCount") -fun List.hasSameContentAs(other: List): Boolean { - if (this.size != other.size) return false - - if (this === other) return true - - for (i in this.indices) { - val a = this[i] - val b = other[i] - - if (a != b) return false - if (a.fileId != b.fileId) return false - if (a.etag != b.etag) return false - if (a.modificationTimestamp != b.modificationTimestamp) return false - - if (a.fileLength != b.fileLength) return false - if (a.isFavorite != b.isFavorite) return false - - if (a.fileName != b.fileName) return false - } - - return true -} - fun List.filterFilenames(): List = distinctBy { it.fileName } fun OCFile.isTempFile(): Boolean { diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 8f7b01694782..2f4bfa23c424 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -1735,48 +1735,74 @@ class FileDisplayActivity : } } - private inner class FileDownloadStartedReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - Log_OC.d(TAG, "download worker started") - handleDownloadWorkerState() + private enum class FileDownloadIndicator(val iconId: Int) { + Downloading(R.drawable.ic_synchronizing), + Downloaded(R.drawable.ic_synced) + } + + private fun updateFileDownloadIndicator(state: FileDownloadIndicator, file: OCFile) { + ocFileListFragment?.adapter?.updateFileIndicator(state.iconId, file) + } + + // region FolderDownloadWorker receivers + @Suppress("ReturnCount") + private fun getFolderFromFolderDownloadWorker(intent: Intent): OCFile? { + val id = intent.getLongExtra(FolderDownloadEventBroadcaster.EXTRA_FILE_ID, -1L) + if (id == -1L) { + Log_OC.e(TAG, "invalid id received") + return null + } + + val folder = storageManager.getFileById(id) + if (folder == null) { + Log_OC.e(TAG, "folder not exists") + return null } + + return folder } - private class FolderDownloadStartedReceiver : BroadcastReceiver() { + private inner class FolderDownloadStartedReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Log_OC.d(TAG, "download worker started") + val folder = getFolderFromFolderDownloadWorker(intent) ?: return + updateFileDownloadIndicator(FileDownloadIndicator.Downloading, folder) } } private inner class FolderDownloadCompletedReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Log_OC.d(TAG, "download worker finished") + val folder = getFolderFromFolderDownloadWorker(intent) ?: return + updateFileDownloadIndicator(FileDownloadIndicator.Downloaded, folder) + } + } + // endregion - val id = intent.getLongExtra(FolderDownloadEventBroadcaster.EXTRA_FILE_ID, -1L) - if (id == -1L) { - Log_OC.e(TAG, "invalid id received") - return - } + // region FileDownloadWorker receivers + private fun getFileFromFileDownloadWorker(intent: Intent): OCFile? { + val remotePath = intent.getStringExtra(FileDownloadEventBroadcaster.EXTRA_REMOTE_PATH) + val file = fileDataStorageManager.getFileByDecryptedRemotePath(remotePath) ?: return null + return file + } - val folder = storageManager.getFileById(id) - if (folder == null) { - Log_OC.e(TAG, "folder not exists") - return + private inner class FileDownloadStartedReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + Log_OC.d(TAG, "download worker started") + getFileFromFileDownloadWorker(intent)?.let { + updateFileDownloadIndicator(FileDownloadIndicator.Downloading, it) } - - ocFileListFragment?.adapter?.notifyItemChanged(folder) + handleDownloadWorkerState() } } - /** - * Class waiting for broadcast events from the [FileDownloadWorker] service. - * - * - * Updates the UI when a download is started or finished, provided that it is relevant for the current folder. - */ private inner class FileDownloadCompletedReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent) { Log_OC.d(TAG, "file download completed received") + getFileFromFileDownloadWorker(intent)?.let { + updateFileDownloadIndicator(FileDownloadIndicator.Downloaded, it) + } + fileDownloadProgressListener = null if (fileIDForImmediatePreview == -1L) { @@ -1857,6 +1883,7 @@ class FileDisplayActivity : return accountName != null && account != null && accountName == account.name } } + // endregion fun browseToRoot() { val listOfFiles = this.listOfFilesFragment @@ -2356,7 +2383,7 @@ class FileDisplayActivity : private fun requestForDownload() { val user = user.orElseThrow(Supplier { RuntimeException() }) mWaitingToPreview?.let { - FileDownloadHelper.Companion.instance().downloadFileIfNotStartedBefore(user, it) + FileDownloadHelper.instance().downloadFileIfNotStartedBefore(user, it) } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index 6ecfdcbaa495..ca172e9c86cb 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -33,7 +33,6 @@ import com.nextcloud.client.jobs.upload.FileUploadHelper; import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.model.OfflineOperationType; -import com.nextcloud.utils.extensions.OCFileExtensionsKt; import com.nextcloud.utils.extensions.ViewExtensionsKt; import com.nextcloud.utils.mdm.MDMConfig; import com.owncloud.android.MainApp; @@ -871,13 +870,6 @@ public void swapDirectory( } public void updateAdapter(List newFiles, OCFile directory) { - boolean hasSameContent = OCFileExtensionsKt.hasSameContentAs(mFiles, newFiles); - - if (hasSameContent) { - Log_OC.d(TAG, "same data passed skipping update"); - return; - } - Log_OC.d(TAG, "updating the adapter"); mFiles = new ArrayList<>(newFiles); @@ -1085,6 +1077,27 @@ public void setCurrentDirectory(OCFile folder) { currentDirectory = folder; } + // payload only for local file indicator + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List payloads) { + if (!payloads.isEmpty() && payloads.get(0) instanceof Integer iconId && holder instanceof ListViewHolder listViewHolder) { + listViewHolder.getLocalFileIndicator().setImageResource(iconId); + listViewHolder.getLocalFileIndicator().setVisibility(View.VISIBLE); + // skip full rebind + return; + } + super.onBindViewHolder(holder, position, payloads); + } + + public void updateFileIndicator(int iconId, OCFile file) { + if (file == null) return; + + int position = getItemPosition(file); + if (position != -1) { + notifyItemChanged(position, iconId); + } + } + public void cleanup() { ocFileListDelegate.cleanup(); helper.cleanup(); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt index ff3d00515cf2..e61928757861 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt @@ -20,7 +20,6 @@ import com.nextcloud.client.jobs.gallery.GalleryImageGenerationListener import com.nextcloud.client.jobs.upload.FileUploadHelper import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.utils.OCFileUtils -import com.nextcloud.utils.extensions.getSubfiles import com.nextcloud.utils.extensions.makeRounded import com.nextcloud.utils.extensions.setVisibleIf import com.nextcloud.utils.mdm.MDMConfig @@ -45,7 +44,6 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.job import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext @Suppress("LongParameterList", "TooManyFunctions") class OCFileListDelegate( @@ -337,13 +335,6 @@ class OCFileListDelegate( } } - private suspend fun isFolderFullyDownloaded(file: OCFile): Boolean = withContext(Dispatchers.IO) { - file.isFolder && - storageManager.getSubfiles(file.fileId, user.accountName) - .takeIf { it.isNotEmpty() } - ?.all { it.isDown } == true - } - private fun isSynchronizing(file: OCFile): Boolean { val operationsServiceBinder = transferServiceGetter.operationsServiceBinder val fileDownloadHelper = FileDownloadHelper.instance() @@ -354,28 +345,24 @@ class OCFileListDelegate( } private fun showLocalFileIndicator(file: OCFile, holder: ListViewHolder) { - ioScope.launch { - val isFullyDownloaded = isFolderFullyDownloaded(file) - val isSyncing = isSynchronizing(file) - val hasConflict = (file.etagInConflict != null) - val isDown = file.isDown - - val icon = when { - isSyncing -> R.drawable.ic_synchronizing - hasConflict -> R.drawable.ic_synchronizing_error - isDown || isFullyDownloaded -> R.drawable.ic_synced - else -> null - } + val isFullyDownloaded = storageManager.fileDao.isFolderFullyDownloaded(file.fileId, user.accountName) + val isSyncing = isSynchronizing(file) + val hasConflict = (file.etagInConflict != null) + val isDown = file.isDown + + val icon = when { + isSyncing -> R.drawable.ic_synchronizing + hasConflict -> R.drawable.ic_synchronizing_error + isDown || isFullyDownloaded -> R.drawable.ic_synced + else -> null + } - withContext(Dispatchers.Main) { - holder.localFileIndicator.run { - if (icon != null && showMetadata) { - setImageResource(icon) - visibility = View.VISIBLE - } else { - visibility = View.GONE - } - } + holder.localFileIndicator.run { + if (icon != null && showMetadata) { + setImageResource(icon) + visibility = View.VISIBLE + } else { + visibility = View.GONE } } }