Skip to content

Commit 1e565db

Browse files
authored
Merge pull request #3977 from owncloud/feature/discovery_enhancement
[FEATURE] Account discovery enhancement
2 parents 47eef1c + 83b5e05 commit 1e565db

7 files changed

Lines changed: 168 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ ownCloud admins and users.
88
Summary
99
-------
1010

11+
* Change - Bump target SDK to 33: [#3617](https://github.com/owncloud/android/issues/3617)
1112
* Enhancement - Support for spaces: [#3851](https://github.com/owncloud/android/pull/3851)
1213
* Enhancement - Update label on Camera Uploads: [#3930](https://github.com/owncloud/android/pull/3930)
1314
* Enhancement - Authenticated WebFinger: [#3943](https://github.com/owncloud/android/issues/3943)
@@ -16,6 +17,15 @@ Summary
1617
Details
1718
-------
1819

20+
* Change - Bump target SDK to 33: [#3617](https://github.com/owncloud/android/issues/3617)
21+
22+
Target SDK was upgraded to 33 to keep the app updated with the latest android changes. A new
23+
setting was introduced to manage notifications in an easier way.
24+
25+
https://github.com/owncloud/android/issues/3617
26+
https://github.com/owncloud/android/pull/3972
27+
https://developer.android.com/about/versions/13/behavior-changes-13
28+
1929
* Enhancement - Support for spaces: [#3851](https://github.com/owncloud/android/pull/3851)
2030

2131
Spaces are now supported in oCIS accounts. A new tab has been added, which allows to list and

owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ val viewModelModule = module {
7575
PassCodeViewModel(get(), get(), action)
7676
}
7777

78-
viewModel { AuthenticationViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
78+
viewModel { AuthenticationViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
7979
viewModel { OAuthViewModel(get(), get(), get(), get()) }
8080
viewModel { SettingsViewModel(get()) }
8181
viewModel { SettingsSecurityViewModel(get(), get()) }

owncloudApp/src/main/java/com/owncloud/android/presentation/authentication/AuthenticationViewModel.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import com.owncloud.android.domain.webfinger.usecases.GetOwnCloudInstancesFromAu
4040
import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResult
4141
import com.owncloud.android.presentation.common.UIResult
4242
import com.owncloud.android.providers.CoroutinesDispatcherProvider
43+
import com.owncloud.android.providers.WorkManagerProvider
4344
import kotlinx.coroutines.launch
4445
import timber.log.Timber
4546

@@ -54,6 +55,7 @@ class AuthenticationViewModel(
5455
private val refreshCapabilitiesFromServerAsyncUseCase: RefreshCapabilitiesFromServerAsyncUseCase,
5556
private val getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase,
5657
private val refreshSpacesFromServerAsyncUseCase: RefreshSpacesFromServerAsyncUseCase,
58+
private val workManagerProvider: WorkManagerProvider,
5759
private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider,
5860
) : ViewModel() {
5961

@@ -213,5 +215,6 @@ class AuthenticationViewModel(
213215
}
214216
_accountDiscovery.postValue(Event(UIResult.Success()))
215217
}
218+
workManagerProvider.enqueueAccountDiscovery(accountName)
216219
}
217220
}

owncloudApp/src/main/java/com/owncloud/android/providers/WorkManagerProvider.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ import androidx.lifecycle.LiveData
2424
import androidx.work.Constraints
2525
import androidx.work.ExistingPeriodicWorkPolicy
2626
import androidx.work.NetworkType
27+
import androidx.work.OneTimeWorkRequestBuilder
2728
import androidx.work.PeriodicWorkRequestBuilder
2829
import androidx.work.WorkInfo
2930
import androidx.work.WorkManager
31+
import androidx.work.workDataOf
3032
import com.owncloud.android.extensions.getRunningWorkInfosLiveData
33+
import com.owncloud.android.workers.AccountDiscoveryWorker
3134
import com.owncloud.android.workers.AvailableOfflinePeriodicWorker
3235
import com.owncloud.android.workers.AvailableOfflinePeriodicWorker.Companion.AVAILABLE_OFFLINE_PERIODIC_WORKER
3336
import com.owncloud.android.workers.CameraUploadsWorker
@@ -82,6 +85,22 @@ class WorkManagerProvider(
8285
.enqueueUniquePeriodicWork(AVAILABLE_OFFLINE_PERIODIC_WORKER, ExistingPeriodicWorkPolicy.KEEP, availableOfflinePeriodicWorker)
8386
}
8487

88+
fun enqueueAccountDiscovery(accountName: String) {
89+
val constraintsRequired = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
90+
91+
val inputData = workDataOf(
92+
AccountDiscoveryWorker.KEY_PARAM_DISCOVERY_ACCOUNT to accountName,
93+
)
94+
95+
val accountDiscoveryWorker = OneTimeWorkRequestBuilder<AccountDiscoveryWorker>()
96+
.setInputData(inputData)
97+
.addTag(accountName)
98+
.setConstraints(constraintsRequired)
99+
.build()
100+
101+
WorkManager.getInstance(context).enqueue(accountDiscoveryWorker)
102+
}
103+
85104
fun getRunningUploadsWorkInfosLiveData(): LiveData<List<WorkInfo>> {
86105
return WorkManager.getInstance(context).getRunningWorkInfosLiveData(
87106
listOf(
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* ownCloud Android client application
3+
*
4+
* @author Abel García de Prada
5+
* @author Juan Carlos Garrote Gascón
6+
*
7+
* Copyright (C) 2023 ownCloud GmbH.
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License version 2,
11+
* as published by the Free Software Foundation.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU General Public License
19+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
*/
21+
22+
package com.owncloud.android.workers
23+
24+
import android.content.Context
25+
import androidx.work.CoroutineWorker
26+
import androidx.work.WorkerParameters
27+
import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
28+
import com.owncloud.android.domain.capabilities.usecases.RefreshCapabilitiesFromServerAsyncUseCase
29+
import com.owncloud.android.domain.files.model.OCFile
30+
import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH
31+
import com.owncloud.android.domain.files.usecases.GetFileByRemotePathUseCase
32+
import com.owncloud.android.domain.spaces.usecases.GetPersonalAndProjectSpacesForAccountUseCase
33+
import com.owncloud.android.domain.spaces.usecases.RefreshSpacesFromServerAsyncUseCase
34+
import com.owncloud.android.presentation.authentication.AccountUtils
35+
import com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase
36+
import org.koin.core.component.KoinComponent
37+
import org.koin.core.component.inject
38+
import timber.log.Timber
39+
40+
class AccountDiscoveryWorker(
41+
private val appContext: Context,
42+
private val workerParameters: WorkerParameters
43+
) : CoroutineWorker(
44+
appContext,
45+
workerParameters
46+
), KoinComponent {
47+
48+
private val refreshCapabilitiesFromServerAsyncUseCase: RefreshCapabilitiesFromServerAsyncUseCase by inject()
49+
private val getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase by inject()
50+
private val refreshSpacesFromServerAsyncUseCase: RefreshSpacesFromServerAsyncUseCase by inject()
51+
private val getPersonalAndProjectSpacesForAccountUseCase: GetPersonalAndProjectSpacesForAccountUseCase by inject()
52+
private val getFileByRemotePathUseCase: GetFileByRemotePathUseCase by inject()
53+
private val synchronizeFolderUseCase: SynchronizeFolderUseCase by inject()
54+
55+
override suspend fun doWork(): Result {
56+
val accountName = workerParameters.inputData.getString(KEY_PARAM_DISCOVERY_ACCOUNT)
57+
val account = AccountUtils.getOwnCloudAccountByName(appContext, accountName)
58+
Timber.d("Account Discovery for account: $accountName and accountName: ${account.name}")
59+
60+
if (accountName.isNullOrBlank() || account == null) return Result.failure()
61+
62+
// 1. Refresh capabilities for account
63+
refreshCapabilitiesFromServerAsyncUseCase.execute(RefreshCapabilitiesFromServerAsyncUseCase.Params(accountName))
64+
val capabilities = getStoredCapabilitiesUseCase.execute(GetStoredCapabilitiesUseCase.Params(accountName))
65+
66+
val spacesAvailableForAccount = AccountUtils.isSpacesFeatureAllowedForAccount(appContext, account, capabilities)
67+
68+
// 2.1 Account does not support spaces
69+
if (!spacesAvailableForAccount) {
70+
val rootLegacyFolder = getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(accountName, ROOT_PATH, null)).getDataOrNull()
71+
rootLegacyFolder?.let {
72+
discoverRootFolder(it)
73+
}
74+
} else {
75+
val spacesRootFoldersToDiscover = mutableListOf<OCFile>()
76+
77+
// 2.2 Account does support spaces
78+
refreshSpacesFromServerAsyncUseCase.execute(RefreshSpacesFromServerAsyncUseCase.Params(accountName))
79+
val spaces = getPersonalAndProjectSpacesForAccountUseCase.execute(GetPersonalAndProjectSpacesForAccountUseCase.Params(accountName))
80+
81+
// First we discover the root of the personal space since it is the first thing seen after login
82+
val personalSpace = spaces.firstOrNull { it.isPersonal }
83+
personalSpace?.let { space ->
84+
val rootFolderForSpace =
85+
getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(accountName, ROOT_PATH, space.root.id)).getDataOrNull()
86+
rootFolderForSpace?.let {
87+
discoverRootFolder(it)
88+
}
89+
}
90+
91+
// Then we discover the root of the rest of spaces
92+
val spacesWithoutPersonal = spaces.filterNot { it.isPersonal }
93+
spacesWithoutPersonal.forEach { space ->
94+
// Create the root file for each space
95+
val rootFolderForSpace =
96+
getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(accountName, ROOT_PATH, space.root.id)).getDataOrNull()
97+
rootFolderForSpace?.let {
98+
spacesRootFoldersToDiscover.add(it)
99+
}
100+
}
101+
spacesRootFoldersToDiscover.forEach {
102+
discoverRootFolder(it)
103+
}
104+
}
105+
106+
return Result.success()
107+
}
108+
109+
private fun discoverRootFolder(folder: OCFile) {
110+
synchronizeFolderUseCase.execute(
111+
SynchronizeFolderUseCase.Params(
112+
accountName = folder.owner,
113+
remotePath = folder.remotePath,
114+
spaceId = folder.spaceId,
115+
syncMode = SynchronizeFolderUseCase.SyncFolderMode.REFRESH_FOLDER
116+
)
117+
)
118+
}
119+
120+
companion object {
121+
const val KEY_PARAM_DISCOVERY_ACCOUNT = "KEY_PARAM_DISCOVERY_ACCOUNT"
122+
}
123+
}

owncloudApp/src/test/java/com/owncloud/android/presentation/viewmodels/authentication/AuthenticationViewModelTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import com.owncloud.android.presentation.authentication.AuthenticationViewModel
3636
import com.owncloud.android.presentation.common.UIResult
3737
import com.owncloud.android.presentation.viewmodels.ViewModelTest
3838
import com.owncloud.android.providers.ContextProvider
39+
import com.owncloud.android.providers.WorkManagerProvider
3940
import com.owncloud.android.testutil.OC_ACCESS_TOKEN
4041
import com.owncloud.android.testutil.OC_ACCOUNT_NAME
4142
import com.owncloud.android.testutil.OC_AUTH_TOKEN_TYPE
@@ -73,6 +74,7 @@ class AuthenticationViewModelTest : ViewModelTest() {
7374
private lateinit var refreshSpacesFromServerAsyncUseCase: RefreshSpacesFromServerAsyncUseCase
7475
private lateinit var refreshCapabilitiesFromServerAsyncUseCase: RefreshCapabilitiesFromServerAsyncUseCase
7576
private lateinit var getStoredCapabilitiesUseCase: GetStoredCapabilitiesUseCase
77+
private lateinit var workManagerProvider: WorkManagerProvider
7678
private lateinit var contextProvider: ContextProvider
7779

7880
private val commonException = ServerNotReachableException()
@@ -103,6 +105,7 @@ class AuthenticationViewModelTest : ViewModelTest() {
103105
getOwnCloudInstancesFromAuthenticatedWebFingerUseCase = mockk()
104106
refreshCapabilitiesFromServerAsyncUseCase = mockk()
105107
refreshSpacesFromServerAsyncUseCase = mockk()
108+
workManagerProvider = mockk(relaxUnitFun = true)
106109
getStoredCapabilitiesUseCase = mockk()
107110

108111
testCoroutineDispatcher.pauseDispatcher()
@@ -118,6 +121,7 @@ class AuthenticationViewModelTest : ViewModelTest() {
118121
refreshCapabilitiesFromServerAsyncUseCase = refreshCapabilitiesFromServerAsyncUseCase,
119122
refreshSpacesFromServerAsyncUseCase = refreshSpacesFromServerAsyncUseCase,
120123
getStoredCapabilitiesUseCase = getStoredCapabilitiesUseCase,
124+
workManagerProvider = workManagerProvider,
121125
coroutinesDispatcherProvider = coroutineDispatcherProvider
122126
)
123127
}

owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import androidx.room.Insert
2626
import androidx.room.OnConflictStrategy
2727
import androidx.room.Query
2828
import androidx.room.Transaction
29+
import androidx.room.Update
2930
import androidx.room.Upsert
3031
import com.owncloud.android.data.ProviderMeta
3132
import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus.AVAILABLE_OFFLINE
@@ -132,6 +133,9 @@ interface FileDao {
132133
@Insert(onConflict = OnConflictStrategy.IGNORE)
133134
fun insertOrIgnore(ocFileEntity: OCFileEntity): Long
134135

136+
@Update
137+
fun updateFile(ocFileEntity: OCFileEntity)
138+
135139
@Upsert
136140
fun upsert(ocFileEntity: OCFileEntity)
137141

@@ -193,7 +197,10 @@ interface FileDao {
193197
): List<OCFileEntity> {
194198
var folderId = insertOrIgnore(folder)
195199
// If it was already in database
196-
if (folderId == -1L) folderId = folder.id
200+
if (folderId == -1L) {
201+
updateFile(folder)
202+
folderId = folder.id
203+
}
197204

198205
folderContent.forEach { fileToInsert ->
199206
upsert(fileToInsert.apply {

0 commit comments

Comments
 (0)