Skip to content

[FIX] Image autoupload uploads images twice under certain conditions #4571

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,20 @@ ownCloud admins and users.

## Summary

* Bugfix - Add bottom margin for used quota in account dialog: [#4566](https://github.com/owncloud/android/issues/4566)
* Change - Bump target SDK to 35: [#4529](https://github.com/owncloud/android/issues/4529)
* Change - Replace dav4android location: [#4536](https://github.com/owncloud/android/issues/4536)

## Details

* Bugfix - Add bottom margin for used quota in account dialog: [#4566](https://github.com/owncloud/android/issues/4566)

Added bottom margin to the container holding used quota view when multi account
is disabled

https://github.com/owncloud/android/issues/4566
https://github.com/owncloud/android/pull/4567

* Change - Bump target SDK to 35: [#4529](https://github.com/owncloud/android/issues/4529)

Target SDK has been upgraded to 35 in order to fulfill Android platform
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ androidxTest = "1.4.0"
androidxTestExt = "1.1.5"
androidxTestMonitor = "1.6.1"
androidxTestUiAutomator ="2.2.0"
androidxWork = "2.8.1"
androidxWork = "2.10.1"
coil = "2.2.2"
detekt = "1.23.3"
dexopener = "2.0.5"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ package com.owncloud.android.workers

import android.content.Context
import android.net.Uri
import android.os.Build
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.work.CoroutineWorker
Expand Down Expand Up @@ -50,6 +51,7 @@ import org.koin.core.component.inject
import timber.log.Timber
import java.io.File
import java.util.Date
import java.util.concurrent.CancellationException
import java.util.concurrent.TimeUnit

class AutomaticUploadsWorker(
Expand All @@ -75,41 +77,50 @@ class AutomaticUploadsWorker(
private val transferRepository: TransferRepository by inject()

override suspend fun doWork(): Result {
Timber.i("Starting AutomaticUploadsWorker with UUID ${this.id}")
when (val useCaseResult = getAutomaticUploadsConfigurationUseCase(Unit)) {
is UseCaseResult.Success -> {
val cameraUploadsConfiguration = useCaseResult.data
if (cameraUploadsConfiguration == null || cameraUploadsConfiguration.areAutomaticUploadsDisabled()) {
cancelWorker()
return Result.success()
}
cameraUploadsConfiguration.pictureUploadsConfiguration?.let { pictureUploadsConfiguration ->
try {
checkSourcePathIsAValidUriOrThrowException(pictureUploadsConfiguration.sourcePath)
syncFolder(pictureUploadsConfiguration)
} catch (illegalArgumentException: IllegalArgumentException) {
Timber.e(illegalArgumentException, "Source path for picture uploads is not valid")
showNotificationToUpdateUri(SyncType.PICTURE_UPLOADS)
return Result.failure()
try {
Timber.i("Starting AutomaticUploadsWorker with UUID ${this.id}")
when (val useCaseResult = getAutomaticUploadsConfigurationUseCase(Unit)) {
is UseCaseResult.Success -> {
val cameraUploadsConfiguration = useCaseResult.data
if (cameraUploadsConfiguration == null || cameraUploadsConfiguration.areAutomaticUploadsDisabled()) {
cancelWorker()
return Result.success()
}
}
cameraUploadsConfiguration.videoUploadsConfiguration?.let { videoUploadsConfiguration ->
try {
checkSourcePathIsAValidUriOrThrowException(videoUploadsConfiguration.sourcePath)
syncFolder(videoUploadsConfiguration)
} catch (illegalArgumentException: IllegalArgumentException) {
Timber.e(illegalArgumentException, "Source path for video uploads is not valid")
showNotificationToUpdateUri(SyncType.VIDEO_UPLOADS)
return Result.failure()
cameraUploadsConfiguration.pictureUploadsConfiguration?.let { pictureUploadsConfiguration ->
try {
checkSourcePathIsAValidUriOrThrowException(pictureUploadsConfiguration.sourcePath)
syncFolder(pictureUploadsConfiguration)
} catch (illegalArgumentException: IllegalArgumentException) {
Timber.e(illegalArgumentException, "Source path for picture uploads is not valid")
showNotificationToUpdateUri(SyncType.PICTURE_UPLOADS)
return Result.failure()
}
}
cameraUploadsConfiguration.videoUploadsConfiguration?.let { videoUploadsConfiguration ->
try {
checkSourcePathIsAValidUriOrThrowException(videoUploadsConfiguration.sourcePath)
syncFolder(videoUploadsConfiguration)
} catch (illegalArgumentException: IllegalArgumentException) {
Timber.e(illegalArgumentException, "Source path for video uploads is not valid")
showNotificationToUpdateUri(SyncType.VIDEO_UPLOADS)
return Result.failure()
}
}
}
is UseCaseResult.Error -> {
Timber.e(useCaseResult.throwable, "Worker ${useCaseResult.throwable}")
}
}
is UseCaseResult.Error -> {
Timber.e(useCaseResult.throwable, "Worker ${useCaseResult.throwable}")
Timber.i("Finishing CameraUploadsWorker with UUID ${this.id}")
return Result.success()
} catch (e: CancellationException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
Timber.i("AutomaticUploadsWorker with UUID ${this.id} was cancelled. Error: $stopReason")
} else {
Timber.i("AutomaticUploadsWorker with UUID ${this.id} was cancelled.")
}
return Result.failure()
}
Timber.i("Finishing CameraUploadsWorker with UUID ${this.id}")
return Result.success()
}

@Throws(IllegalArgumentException::class)
Expand All @@ -122,9 +133,7 @@ class AutomaticUploadsWorker(
WorkManager.getInstance(appContext).cancelUniqueWork(AUTOMATIC_UPLOADS_WORKER)
}

private fun syncFolder(folderBackUpConfiguration: FolderBackUpConfiguration?) {
if (folderBackUpConfiguration == null) return

private fun syncFolder(folderBackUpConfiguration: FolderBackUpConfiguration) {
val syncType = when {
folderBackUpConfiguration.isPictureUploads -> SyncType.PICTURE_UPLOADS
folderBackUpConfiguration.isVideoUploads -> SyncType.VIDEO_UPLOADS
Expand All @@ -133,6 +142,9 @@ class AutomaticUploadsWorker(
}

val currentTimestamp = System.currentTimeMillis()
updateTimestamp(folderBackUpConfiguration, syncType, currentTimestamp)

Timber.i("Timestamp updated correctly. Current timestamp: ${Date(currentTimestamp)}")

val localPicturesDocumentFiles: List<DocumentFile> = getFilesReadyToUpload(
syncType = syncType,
Expand All @@ -141,6 +153,8 @@ class AutomaticUploadsWorker(
currentTimestamp = currentTimestamp,
)

Timber.i("The search for files to upload has finished")

showNotification(syncType, localPicturesDocumentFiles.size)

for (documentFile in localPicturesDocumentFiles) {
Expand All @@ -166,7 +180,8 @@ class AutomaticUploadsWorker(
chargingOnly = folderBackUpConfiguration.chargingOnly
)
}
updateTimestamp(folderBackUpConfiguration, syncType, currentTimestamp)

Timber.i("Folder synchronization has finished")
}

private fun showNotification(
Expand Down Expand Up @@ -244,9 +259,17 @@ class AutomaticUploadsWorker(
currentTimestamp: Long,
): List<DocumentFile> {
val sourceUri: Uri = sourcePath.toUri()

Timber.i ("Source uri is: $sourceUri")

val documentTree = DocumentFile.fromTreeUri(applicationContext, sourceUri)

Timber.i ("Document tree is: $documentTree")

val arrayOfLocalFiles = documentTree?.listFiles() ?: arrayOf()

Timber.i("The search of local files has finished. Now, all files are going to be filtered")

val filteredList: List<DocumentFile> = arrayOfLocalFiles
.sortedBy { it.lastModified() }
.filter { it.lastModified() >= lastSyncTimestamp }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.dsl.module

@Ignore("Test not working due to update of testCoroutineDispatcher")
@ExperimentalCoroutinesApi
class DrawerViewModelTest : ViewModelTest() {
private lateinit var drawerViewModel: DrawerViewModel
Expand Down Expand Up @@ -69,8 +71,6 @@ class DrawerViewModelTest : ViewModelTest() {
getUserQuotasUseCase = mockk()
localStorageProvider = mockk()

testCoroutineDispatcher.pauseDispatcher()

drawerViewModel = DrawerViewModel(
getStoredQuotaAsStreamUseCase = getStoredQuotaAsStreamUseCase,
removeAccountUseCase = removeAccountUseCase,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import com.owncloud.android.testutil.livedata.getEmittedValues
import io.mockk.unmockkAll
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.resetMain
import org.junit.After
import org.junit.Assert.assertEquals
Expand All @@ -41,7 +41,7 @@ open class ViewModelTest {
@JvmField
val instantExecutorRule = InstantTaskExecutorRule()

val testCoroutineDispatcher = TestCoroutineDispatcher()
val testCoroutineDispatcher = StandardTestDispatcher()
val coroutineDispatcherProvider: CoroutinesDispatcherProvider = CoroutinesDispatcherProvider(
io = testCoroutineDispatcher,
main = testCoroutineDispatcher,
Expand All @@ -51,7 +51,6 @@ open class ViewModelTest {
@After
open fun tearDown() {
Dispatchers.resetMain()
testCoroutineDispatcher.cleanupTestCoroutines()

unmockkAll()
}
Expand All @@ -60,9 +59,7 @@ open class ViewModelTest {
expectedValues: List<Event<UIResult<DomainModel>>>,
liveData: LiveData<Event<UIResult<DomainModel>>>
) {
val emittedValues = liveData.getEmittedValues(expectedValues.size) {
testCoroutineDispatcher.resumeDispatcher()
}
val emittedValues = liveData.getEmittedValues(expectedValues.size)
assertEquals(expectedValues, emittedValues)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.dsl.module

@Ignore("Test not working due to update of testCoroutineDispatcher")
@ExperimentalCoroutinesApi
class AuthenticationViewModelTest : ViewModelTest() {
private lateinit var authenticationViewModel: AuthenticationViewModel
Expand Down Expand Up @@ -129,8 +131,6 @@ class AuthenticationViewModelTest : ViewModelTest() {
every { contextProvider.getBoolean(R.bool.enforce_secure_connection) } returns false
every { contextProvider.getBoolean(R.bool.enforce_oidc) } returns false

testCoroutineDispatcher.pauseDispatcher()

authenticationViewModel = AuthenticationViewModel(
loginBasicAsyncUseCase = loginBasicAsyncUseCase,
loginOAuthAsyncUseCase = loginOAuthAsyncUseCase,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.dsl.module

@Ignore("Test not working due to update of testCoroutineDispatcher")
@ExperimentalCoroutinesApi
class OAuthViewModelTest : ViewModelTest() {
private lateinit var oAuthViewModel: OAuthViewModel
Expand Down Expand Up @@ -87,8 +89,6 @@ class OAuthViewModelTest : ViewModelTest() {
requestTokenUseCase = mockk()
registerClientUseCase = mockk()

testCoroutineDispatcher.pauseDispatcher()

oAuthViewModel = OAuthViewModel(
getOIDCDiscoveryUseCase = getOIDCDiscoveryUseCase,
requestTokenUseCase = requestTokenUseCase,
Expand Down
Loading