From c32ccbf1bf0614a4a27e802aea0b0f1839c438f8 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 09:17:11 +0200 Subject: [PATCH 1/5] add quota check for all uploads Signed-off-by: alperozturk96 --- .../com/nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt | 2 +- .../java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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..660cca7085f6 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 @@ -273,7 +273,7 @@ class AlbumFileUploadWorker( if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "Quota exceeded, stopping uploads") notificationManager.showQuotaExceedNotification(operation) - break + 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..a2b5be4de852 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 @@ -349,7 +349,7 @@ class FileUploadWorker( if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "Quota exceeded, stopping uploads") notificationManager.showQuotaExceedNotification(operation) - break + return@withContext Result.failure() } if (!result.isSuccess && !result.code.isUserCancellation()) { From 3aca95ba307f1739390fd0261e4823e5b7facb64 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 09:19:00 +0200 Subject: [PATCH 2/5] add quota check for auto upload Signed-off-by: alperozturk96 --- .../com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt | 5 +++++ 1 file changed, 5 insertions(+) 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..ed8ada008796 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,11 @@ class AutoUploadWorker( return@withContext } + if (result.code == ResultCode.QUOTA_EXCEEDED) { + Log_OC.w(TAG, "quota exceeded, stopping auto upload") + return@withContext + } + // Mark CONFLICT files as handled to prevent retries if (result.code.isConflict()) { repository.markFileAsHandled(localPath, syncedFolder) From f91acf1d4f4f00fa45884b51d7cb81abf593c774 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 13 Oct 2026 09:25:52 +0200 Subject: [PATCH 3/5] use lib Signed-off-by: alperozturk96 # Conflicts: # gradle/libs.versions.toml --- gradle/verification-metadata.xml | 8 ++++++++ 1 file changed, 8 insertions(+) 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 @@ + + + + + + + + From 1a489a04855210ab869ad3c140be5afa1d2f9e0a Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 4 Nov 2026 14:16:30 +0300 Subject: [PATCH 4/5] recover from exceed state Signed-off-by: alperozturk96 --- .../client/database/dao/UploadDao.kt | 16 +++++++++++ .../jobs/upload/AlbumFileUploadWorker.kt | 2 +- .../client/jobs/upload/FileUploadWorker.kt | 2 +- .../jobs/upload/UploadNotificationManager.kt | 11 -------- .../utils/UploadErrorNotificationManager.kt | 5 ++++ .../AppWideNotificationManager.kt | 28 +++++++++++++++++-- .../extensions/UploadResultExtensions.kt | 1 - .../datamodel/UploadsStorageManager.kt | 11 ++++++++ app/src/main/res/values/strings.xml | 1 + 9 files changed, 60 insertions(+), 17 deletions(-) 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/upload/AlbumFileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt index 660cca7085f6..98cbac7366f8 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,7 +272,7 @@ class AlbumFileUploadWorker( if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "Quota exceeded, stopping uploads") - notificationManager.showQuotaExceedNotification(operation) + uploadsStorageManager.markInProgressUploadsAsQuotaExceeded(accountName) return@withContext Result.failure() } 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 a2b5be4de852..29c416a7d43f 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,7 +348,7 @@ class FileUploadWorker( // check quota first if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "Quota exceeded, stopping uploads") - notificationManager.showQuotaExceedNotification(operation) + uploadsStorageManager.markInProgressUploadsAsQuotaExceeded(accountName) return@withContext Result.failure() } 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/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 From 0775491176ffd5c1b31079c0397094a0cacba4f4 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 4 Nov 2026 15:44:28 +0300 Subject: [PATCH 5/5] notify upload list Signed-off-by: alperozturk96 --- .../nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt | 1 + .../nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt | 1 + .../com/nextcloud/client/jobs/upload/FileUploadWorker.kt | 1 + .../android/ui/adapter/uploadList/UploadListAdapter.kt | 7 +++++++ .../android/ui/fragment/uploadList/UploadListFragment.kt | 5 +++++ 5 files changed, 15 insertions(+) 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 ed8ada008796..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 @@ -367,6 +367,7 @@ class AutoUploadWorker( if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "quota exceeded, stopping auto upload") + sendUploadFinishEvent(operation, result) return@withContext } 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 98cbac7366f8..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 @@ -273,6 +273,7 @@ class AlbumFileUploadWorker( if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "Quota exceeded, stopping uploads") uploadsStorageManager.markInProgressUploadsAsQuotaExceeded(accountName) + fileUploadEventBroadcaster.sendUploadCompleted(operation, result, context) return@withContext Result.failure() } 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 29c416a7d43f..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 @@ -349,6 +349,7 @@ class FileUploadWorker( if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "Quota exceeded, stopping uploads") uploadsStorageManager.markInProgressUploadsAsQuotaExceeded(accountName) + fileUploadEventBroadcaster.sendUploadCompleted(operation, result, context) return@withContext Result.failure() } 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() } } }