diff --git a/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt index 0261666c35ce..653dc03c9050 100644 --- a/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt +++ b/app/src/main/java/com/nextcloud/client/database/dao/UploadDao.kt @@ -90,6 +90,22 @@ interface UploadDao { ) suspend fun updateStatuses(remotePaths: List, accountName: String, status: Int): Int + @Query( + """ + UPDATE ${ProviderTableMeta.UPLOADS_TABLE_NAME} + SET ${ProviderTableMeta.UPLOADS_STATUS} = :newStatus, + ${ProviderTableMeta.UPLOADS_LAST_RESULT} = :lastResult + WHERE ${ProviderTableMeta.UPLOADS_ACCOUNT_NAME} = :accountName + AND ${ProviderTableMeta.UPLOADS_STATUS} = :currentStatus +""" + ) + suspend fun updateStatusAndResultByAccount( + accountName: String, + currentStatus: Int, + newStatus: Int, + lastResult: Int + ): Int + @Query( """ SELECT * FROM ${ProviderTableMeta.UPLOADS_TABLE_NAME} diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index 471a579fda80..b2a70f7ed622 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -365,6 +365,12 @@ class AutoUploadWorker( return@withContext } + if (result.code == ResultCode.QUOTA_EXCEEDED) { + Log_OC.w(TAG, "quota exceeded, stopping auto upload") + sendUploadFinishEvent(operation, result) + return@withContext + } + // Mark CONFLICT files as handled to prevent retries if (result.code.isConflict()) { repository.markFileAsHandled(localPath, syncedFolder) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt index 79c9cd3be930..472071e93544 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt @@ -272,8 +272,9 @@ class AlbumFileUploadWorker( if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "Quota exceeded, stopping uploads") - notificationManager.showQuotaExceedNotification(operation) - break + uploadsStorageManager.markInProgressUploadsAsQuotaExceeded(accountName) + fileUploadEventBroadcaster.sendUploadCompleted(operation, result, context) + return@withContext Result.failure() } sendUploadFinishEvent(totalUploadSize, currentUploadIndex, operation, result) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index 5544949f990e..3d7872b7f4cb 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -348,8 +348,9 @@ class FileUploadWorker( // check quota first if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "Quota exceeded, stopping uploads") - notificationManager.showQuotaExceedNotification(operation) - break + uploadsStorageManager.markInProgressUploadsAsQuotaExceeded(accountName) + fileUploadEventBroadcaster.sendUploadCompleted(operation, result, context) + return@withContext Result.failure() } if (!result.isSuccess && !result.code.isUserCancellation()) { diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt index 30f0ace023c2..9245c5295cf5 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt @@ -91,17 +91,6 @@ class UploadNotificationManager(private val context: Context, viewThemeUtils: Vi ) } - fun showQuotaExceedNotification(operation: UploadFileOperation) { - val notification = notificationBuilder.run { - setContentTitle(context.getString(R.string.upload_quota_exceeded)) - setContentText("") - clearActions() - setProgress(0, 0, false) - }.build() - - showNotification(operation.file.fileId.toInt(), notification) - } - fun showConnectionErrorNotification() { notificationManager.cancel(getId()) diff --git a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt index c8f591180157..c6622a537b67 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt @@ -76,6 +76,11 @@ object UploadErrorNotificationManager { return } + if (result.code == ResultCode.QUOTA_EXCEEDED) { + AppWideNotificationManager.showQuotaExceededNotification(context) + return + } + // do not show an error notification when uploading the same file again if (result.code.isConflict()) { val isSameFile = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/nextcloud/client/notifications/AppWideNotificationManager.kt b/app/src/main/java/com/nextcloud/client/notifications/AppWideNotificationManager.kt index 3bab1e3f890e..c9d3898f221f 100644 --- a/app/src/main/java/com/nextcloud/client/notifications/AppWideNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/notifications/AppWideNotificationManager.kt @@ -8,6 +8,7 @@ package com.nextcloud.client.notifications import android.Manifest +import android.app.Notification import android.app.PendingIntent import android.content.Context import android.content.Intent @@ -37,6 +38,7 @@ object AppWideNotificationManager { private const val SYNC_CONFLICT_NOTIFICATION_INTENT_ACTION_REQ_CODE = 17 private const val SYNC_CONFLICT_NOTIFICATION_ID = 112 + private const val QUOTA_EXCEEDED_NOTIFICATION_ID = 113 fun getUploadListPendingIntent(context: Context): PendingIntent { val intent = NavigatorActivity.intent(context, NavigatorScreen.UploadList).apply { @@ -84,16 +86,36 @@ object AppWideNotificationManager { .setPriority(NotificationCompat.PRIORITY_DEFAULT) .build() + notify(context, SYNC_CONFLICT_NOTIFICATION_ID, notification) + } + + fun showQuotaExceededNotification(context: Context) { + val description = context.getString(R.string.upload_quota_exceeded_notification_description) + + val notification = NotificationCompat.Builder(context, NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD) + .setSmallIcon(R.drawable.uploads) + .setContentTitle(context.getString(R.string.upload_quota_exceeded)) + .setContentText(description) + .setStyle(NotificationCompat.BigTextStyle().bigText(description)) + .setContentIntent(getUploadListPendingIntent(context)) + .setAutoCancel(true) + .setOnlyAlertOnce(true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .build() + + notify(context, QUOTA_EXCEEDED_NOTIFICATION_ID, notification) + } + + private fun notify(context: Context, id: Int, notification: Notification) { if (ActivityCompat.checkSelfPermission( context, Manifest.permission.POST_NOTIFICATIONS ) != PackageManager.PERMISSION_GRANTED ) { - Log_OC.w(TAG, "cannot show sync conflict notification, post notification permission is not granted") + Log_OC.w(TAG, "cannot show notification $id, post notification permission is not granted") return } - NotificationManagerCompat.from(context) - .notify(SYNC_CONFLICT_NOTIFICATION_ID, notification) + NotificationManagerCompat.from(context).notify(id, notification) } } diff --git a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt index ab18d4add992..40f1309e83d8 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt @@ -20,7 +20,6 @@ fun UploadResult.isNonRetryable(): Boolean = when (this) { UploadResult.SAME_FILE_CONFLICT, UploadResult.LOCAL_STORAGE_NOT_COPIED, UploadResult.VIRUS_DETECTED, - UploadResult.QUOTA_EXCEEDED, UploadResult.PRIVILEGES_ERROR, // user's choice diff --git a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt index f7c00065dca1..56706af2d321 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt @@ -197,6 +197,17 @@ class UploadsStorageManager( } } + suspend fun markInProgressUploadsAsQuotaExceeded(accountName: String) { + val count = uploadDao.updateStatusAndResultByAccount( + accountName = accountName, + currentStatus = UploadStatus.UPLOAD_IN_PROGRESS.value, + newStatus = UploadStatus.UPLOAD_FAILED.value, + lastResult = UploadResult.QUOTA_EXCEEDED.value + ) + Log_OC.d(TAG, "marked $count in progress uploads as quota exceeded") + notifyObserversNow() + } + fun removeUpload(upload: OCUpload?): Int = if (upload == null) 0 else removeUpload(upload.uploadId) fun removeUpload(id: Long): Int { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt index 6729649ebc70..b9dc8fd2b686 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt @@ -76,6 +76,7 @@ class UploadListAdapter( ) : SectionedRecyclerViewAdapter() { private val uploadListSections = UploadListSection.sections() + private var latestLoadId = 0 private val showUser: Boolean = accountManager.getAccounts().size > 1 private val uploadHelper = FileUploadHelper.instance() private var uploadProgressListener: UploadProgressListener? = null @@ -559,6 +560,7 @@ class UploadListAdapter( val accountName = optionalUser.get().accountName val capabilities = optionalCapabilities.get() + val loadId = ++latestLoadId activity.lifecycleScope.launch(Dispatchers.IO) { val succeededUploads = uploadHelper.getUploadsByStatus( @@ -578,6 +580,11 @@ class UploadListAdapter( } withContext(Dispatchers.Main) { + if (loadId != latestLoadId) { + onCompleted.run() + return@withContext + } + for (i in uploadListSections.indices) { uploadListSections[i] = updatedSections[i] } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListFragment.kt index fc8d1ebf4827..6395f5a0fd5b 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListFragment.kt @@ -98,6 +98,11 @@ class UploadListFragment : private val uploadFinishReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == FileUploadEventBroadcaster.ACTION_UPLOAD_COMPLETED) { + uploadListAdapter.loadUploadItemsFromDb() + return + } + throttler.run(UPDATE_UPLOAD_LIST_KEY) { uploadListAdapter.loadUploadItemsFromDb() } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5603a2e8f4bc..31901ff1e355 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1161,6 +1161,7 @@ File could not be copied to local storage All uploads are paused Storage quota exceeded + Your storage quota is full, so uploads were stopped. Free up space or ask your administrator for more quota, then retry the failed uploads from Uploads. Server not available Delete entries Toggle expansion of header diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index eea1686f8dde..fd0060558de7 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -22629,6 +22629,14 @@ + + + + + + + +