Skip to content

Commit 262cb56

Browse files
authored
Merge pull request #3851 from owncloud/spaces/main
[SPACES] Main features
2 parents c6562a2 + 8b0478c commit 262cb56

214 files changed

Lines changed: 6218 additions & 1346 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

changelog/unreleased/3851

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Enhancement: Support for spaces
2+
3+
Spaces are now supported in oCIS accounts. A new tab has been added, which allows to list and
4+
browse through all the available spaces for the current account. The supported operations
5+
for files in spaces are: download, upload, remove, rename, create folder, copy and move. The
6+
documents provider has been adapted as well to be able to browse through spaces and perform
7+
the operations already mentioned.
8+
9+
https://github.com/owncloud/android/pull/3851

changelog/unreleased/3945

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Enhancement: Authenticated WebFinger
2+
3+
Authenticated WebFinger was introduced into the authentication flow.
4+
Now, WebFinger is used to retrieve the OpenID Connect issuer and the available ownCloud instances.
5+
For the moment, multiple oC instances are not supported, only the first available instance is used.
6+
7+
https://github.com/owncloud/android/issues/3943
8+
https://github.com/owncloud/android/pull/3945
9+
https://doc.owncloud.com/ocis/next/deployment/services/s-list/webfinger.html

changelog/unreleased/3949

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Enhancement: Link in drawer menu
2+
3+
Customers will be able now to set a personalized label and link that will
4+
appear in the drawer menu, together with the drawer logo as an icon.
5+
6+
https://github.com/owncloud/android/pull/3949
7+
https://github.com/owncloud/android/issues/3907

owncloud-android-library

owncloudApp/src/androidTest/java/com/owncloud/android/authentication/LoginActivityTest.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ package com.owncloud.android.authentication
2222

2323
import android.accounts.AccountManager.KEY_ACCOUNT_NAME
2424
import android.accounts.AccountManager.KEY_ACCOUNT_TYPE
25-
import android.app.Activity
2625
import android.app.Activity.RESULT_OK
2726
import android.content.Context
2827
import android.content.Intent
@@ -46,18 +45,18 @@ import com.owncloud.android.domain.server.model.AuthenticationMethod
4645
import com.owncloud.android.domain.server.model.ServerInfo
4746
import com.owncloud.android.domain.utils.Event
4847
import com.owncloud.android.extensions.parseError
49-
import com.owncloud.android.presentation.common.UIResult
5048
import com.owncloud.android.presentation.authentication.ACTION_UPDATE_EXPIRED_TOKEN
5149
import com.owncloud.android.presentation.authentication.ACTION_UPDATE_TOKEN
50+
import com.owncloud.android.presentation.authentication.AuthenticationViewModel
5251
import com.owncloud.android.presentation.authentication.BASIC_TOKEN_TYPE
5352
import com.owncloud.android.presentation.authentication.EXTRA_ACCOUNT
5453
import com.owncloud.android.presentation.authentication.EXTRA_ACTION
5554
import com.owncloud.android.presentation.authentication.KEY_AUTH_TOKEN_TYPE
5655
import com.owncloud.android.presentation.authentication.LoginActivity
5756
import com.owncloud.android.presentation.authentication.OAUTH_TOKEN_TYPE
58-
import com.owncloud.android.presentation.settings.SettingsActivity
59-
import com.owncloud.android.presentation.authentication.AuthenticationViewModel
6057
import com.owncloud.android.presentation.authentication.oauth.OAuthViewModel
58+
import com.owncloud.android.presentation.common.UIResult
59+
import com.owncloud.android.presentation.settings.SettingsActivity
6160
import com.owncloud.android.presentation.settings.SettingsViewModel
6261
import com.owncloud.android.providers.ContextProvider
6362
import com.owncloud.android.providers.MdmProvider
@@ -107,6 +106,7 @@ class LoginActivityTest {
107106
private lateinit var serverInfoLiveData: MutableLiveData<Event<UIResult<ServerInfo>>>
108107
private lateinit var supportsOauth2LiveData: MutableLiveData<Event<UIResult<Boolean>>>
109108
private lateinit var baseUrlLiveData: MutableLiveData<Event<UIResult<String>>>
109+
private lateinit var accountDiscoveryLiveData: MutableLiveData<Event<UIResult<Unit>>>
110110

111111
@Before
112112
fun setUp() {
@@ -122,11 +122,13 @@ class LoginActivityTest {
122122
serverInfoLiveData = MutableLiveData()
123123
supportsOauth2LiveData = MutableLiveData()
124124
baseUrlLiveData = MutableLiveData()
125+
accountDiscoveryLiveData = MutableLiveData()
125126

126127
every { authenticationViewModel.loginResult } returns loginResultLiveData
127128
every { authenticationViewModel.serverInfo } returns serverInfoLiveData
128129
every { authenticationViewModel.supportsOAuth2 } returns supportsOauth2LiveData
129130
every { authenticationViewModel.baseUrl } returns baseUrlLiveData
131+
every { authenticationViewModel.accountDiscovery } returns accountDiscoveryLiveData
130132
every { settingsViewModel.isThereAttachedAccount() } returns false
131133

132134
stopKoin()
@@ -527,8 +529,9 @@ class LoginActivityTest {
527529
launchTest()
528530

529531
loginResultLiveData.postValue(Event(UIResult.Success(data = "Account_name")))
532+
accountDiscoveryLiveData.postValue(Event(UIResult.Success()))
530533

531-
assertEquals(activityScenario.result.resultCode, Activity.RESULT_OK)
534+
assertEquals(activityScenario.result.resultCode, RESULT_OK)
532535
val accountName: String? = activityScenario.result?.resultData?.extras?.getString(KEY_ACCOUNT_NAME)
533536
val accountType: String? = activityScenario.result?.resultData?.extras?.getString(KEY_ACCOUNT_TYPE)
534537

@@ -543,8 +546,9 @@ class LoginActivityTest {
543546
launchTest(accountType = "notOwnCloud")
544547

545548
loginResultLiveData.postValue(Event(UIResult.Success(data = "Account_name")))
549+
accountDiscoveryLiveData.postValue(Event(UIResult.Success()))
546550

547-
assertEquals(activityScenario.result.resultCode, Activity.RESULT_OK)
551+
assertEquals(activityScenario.result.resultCode, RESULT_OK)
548552
val accountName: String? = activityScenario.result?.resultData?.extras?.getString(KEY_ACCOUNT_NAME)
549553
val accountType: String? = activityScenario.result?.resultData?.extras?.getString(KEY_ACCOUNT_TYPE)
550554

owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
* @author Christian Schabesberger
66
* @author David González Verdugo
77
* @author Abel García de Prada
8+
* @author Juan Carlos Garrote Gascón
89
*
9-
* Copyright (C) 2012 Bartek Przybylski
10-
* Copyright (C) 2020 ownCloud GmbH.
10+
* Copyright (C) 2012 Bartek Przybylski
11+
* Copyright (C) 2023 ownCloud GmbH.
1112
*
1213
* This program is free software: you can redistribute it and/or modify
1314
* it under the terms of the GNU General Public License version 2,
@@ -28,10 +29,15 @@ import android.accounts.Account
2829
import com.owncloud.android.domain.capabilities.model.OCCapability
2930
import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase
3031
import com.owncloud.android.domain.files.model.OCFile
32+
import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH
3133
import com.owncloud.android.domain.files.usecases.GetFileByIdUseCase
3234
import com.owncloud.android.domain.files.usecases.GetFileByRemotePathUseCase
3335
import com.owncloud.android.domain.files.usecases.GetFolderContentUseCase
3436
import com.owncloud.android.domain.files.usecases.GetFolderImagesUseCase
37+
import com.owncloud.android.domain.files.usecases.GetPersonalRootFolderForAccountUseCase
38+
import com.owncloud.android.domain.files.usecases.GetSharesRootFolderForAccount
39+
import com.owncloud.android.domain.spaces.model.OCSpace
40+
import com.owncloud.android.domain.spaces.usecases.GetSpaceWithSpecialsByIdForAccountUseCase
3541
import com.owncloud.android.providers.CoroutinesDispatcherProvider
3642
import kotlinx.coroutines.CoroutineScope
3743
import kotlinx.coroutines.runBlocking
@@ -43,17 +49,40 @@ class FileDataStorageManager(
4349
val account: Account,
4450
) : KoinComponent {
4551

46-
fun getFileByPath(remotePath: String): OCFile? = getFileByPathAndAccount(remotePath, account.name)
52+
fun getFileByPath(remotePath: String, spaceId: String? = null): OCFile? =
53+
if (remotePath == ROOT_PATH && spaceId == null) {
54+
getRootPersonalFolder()
55+
} else {
56+
getFileByPathAndAccount(remotePath, account.name, spaceId)
57+
}
4758

48-
private fun getFileByPathAndAccount(remotePath: String, accountName: String): OCFile? = runBlocking(CoroutinesDispatcherProvider().io) {
59+
private fun getFileByPathAndAccount(remotePath: String, accountName: String, spaceId: String? = null): OCFile? = runBlocking(CoroutinesDispatcherProvider().io) {
4960
val getFileByRemotePathUseCase: GetFileByRemotePathUseCase by inject()
5061

5162
val result = withContext(CoroutineScope(CoroutinesDispatcherProvider().io).coroutineContext) {
52-
getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(accountName, remotePath))
63+
getFileByRemotePathUseCase.execute(GetFileByRemotePathUseCase.Params(accountName, remotePath, spaceId))
5364
}.getDataOrNull()
5465
result
5566
}
5667

68+
fun getRootPersonalFolder() = runBlocking(CoroutinesDispatcherProvider().io) {
69+
val getPersonalRootFolderForAccountUseCase: GetPersonalRootFolderForAccountUseCase by inject()
70+
71+
val result = withContext(CoroutineScope(CoroutinesDispatcherProvider().io).coroutineContext) {
72+
getPersonalRootFolderForAccountUseCase.execute(GetPersonalRootFolderForAccountUseCase.Params(account.name))
73+
}
74+
result
75+
}
76+
77+
fun getRootSharesFolder() = runBlocking(CoroutinesDispatcherProvider().io) {
78+
val getSharesRootFolderForAccount: GetSharesRootFolderForAccount by inject()
79+
80+
val result = withContext(CoroutineScope(CoroutinesDispatcherProvider().io).coroutineContext) {
81+
getSharesRootFolderForAccount.execute(GetSharesRootFolderForAccount.Params(account.name))
82+
}
83+
result
84+
}
85+
5786
// TODO: New_arch: Remove this and call usecase inside FilesViewModel
5887
fun getFileById(id: Long): OCFile? = runBlocking(CoroutinesDispatcherProvider().io) {
5988
val getFileByIdUseCase: GetFileByIdUseCase by inject()
@@ -105,7 +134,16 @@ class FileDataStorageManager(
105134
capability
106135
}
107136

108-
companion object {
109-
const val ROOT_PARENT_ID = 0
137+
fun getSpace(spaceId: String?, accountName: String): OCSpace? = runBlocking(CoroutinesDispatcherProvider().io) {
138+
if (spaceId == null) return@runBlocking null
139+
val getSpaceWithSpecialsByIdForAccountUseCase: GetSpaceWithSpecialsByIdForAccountUseCase by inject()
140+
141+
val space = withContext(CoroutineScope(CoroutinesDispatcherProvider().io).coroutineContext) {
142+
getSpaceWithSpecialsByIdForAccountUseCase.execute(GetSpaceWithSpecialsByIdForAccountUseCase.Params(
143+
spaceId = spaceId,
144+
accountName = accountName,
145+
))
146+
}
147+
return@runBlocking space
110148
}
111149
}

owncloudApp/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import com.owncloud.android.R;
4040
import com.owncloud.android.domain.files.model.OCFile;
4141
import com.owncloud.android.domain.files.usecases.DisableThumbnailsForFileUseCase;
42+
import com.owncloud.android.domain.files.usecases.GetWebDavUrlForSpaceUseCase;
43+
import com.owncloud.android.domain.spaces.model.SpaceSpecial;
4244
import com.owncloud.android.lib.common.OwnCloudAccount;
4345
import com.owncloud.android.lib.common.OwnCloudClient;
4446
import com.owncloud.android.lib.common.SingleSessionManager;
@@ -74,7 +76,8 @@ public class ThumbnailsCacheManager {
7476
private static final int mCompressQuality = 70;
7577
private static OwnCloudClient mClient = null;
7678

77-
private static final String PREVIEW_URI = "%s/remote.php/dav/files/%s%s?x=%d&y=%d&c=%s&preview=1";
79+
private static final String PREVIEW_URI = "%s%s?x=%d&y=%d&c=%s&preview=1";
80+
private static final String SPACE_SPECIAL_URI = "%s?scalingup=0&a=1&x=%d&y=%d&c=%s&preview=1";
7881

7982
public static Bitmap mDefaultImg =
8083
BitmapFactory.decodeResource(
@@ -186,6 +189,8 @@ protected Bitmap doInBackground(Object... params) {
186189
thumbnail = doOCFileInBackground();
187190
} else if (mFile instanceof File) {
188191
thumbnail = doFileInBackground();
192+
} else if (mFile instanceof SpaceSpecial) {
193+
thumbnail = doSpaceImageInBackground();
189194
//} else { do nothing
190195
}
191196

@@ -210,6 +215,8 @@ protected void onPostExecute(Bitmap bitmap) {
210215
tagId = String.valueOf(((OCFile) mFile).getId());
211216
} else if (mFile instanceof File) {
212217
tagId = String.valueOf(mFile.hashCode());
218+
} else if (mFile instanceof SpaceSpecial) {
219+
tagId = ((SpaceSpecial) mFile).getId();
213220
}
214221
if (String.valueOf(imageView.getTag()).equals(tagId)) {
215222
imageView.setImageBitmap(bitmap);
@@ -252,10 +259,18 @@ private int getThumbnailDimension() {
252259
}
253260

254261
private String getPreviewUrl(OCFile ocFile, Account account) {
262+
String baseUrl = mClient.getBaseUri() + "/remote.php/dav/files/" + account.name.split("@")[0];
263+
264+
if (ocFile.getSpaceId() != null) {
265+
Lazy<GetWebDavUrlForSpaceUseCase> getWebDavUrlForSpaceUseCaseLazy = inject(GetWebDavUrlForSpaceUseCase.class);
266+
baseUrl = getWebDavUrlForSpaceUseCaseLazy.getValue().execute(
267+
new GetWebDavUrlForSpaceUseCase.Params(ocFile.getOwner(), ocFile.getSpaceId())
268+
);
269+
270+
}
255271
return String.format(Locale.ROOT,
256272
PREVIEW_URI,
257-
mClient.getBaseUri(),
258-
account.name.split("@")[0],
273+
baseUrl,
259274
Uri.encode(ocFile.getRemotePath(), "/"),
260275
getThumbnailDimension(),
261276
getThumbnailDimension(),
@@ -349,6 +364,64 @@ private Bitmap doFileInBackground() {
349364
return thumbnail;
350365
}
351366

367+
private String getSpaceSpecialUri(SpaceSpecial spaceSpecial) {
368+
// Converts dp to pixel
369+
Resources r = MainApp.Companion.getAppContext().getResources();
370+
Integer spacesThumbnailSize = Math.round(r.getDimension(R.dimen.spaces_thumbnail_height)) * 2;
371+
return String.format(Locale.ROOT,
372+
SPACE_SPECIAL_URI,
373+
spaceSpecial.getWebDavUrl(),
374+
spacesThumbnailSize,
375+
spacesThumbnailSize,
376+
spaceSpecial.getETag());
377+
}
378+
379+
private Bitmap doSpaceImageInBackground() {
380+
SpaceSpecial spaceSpecial = (SpaceSpecial) mFile;
381+
382+
final String imageKey = spaceSpecial.getId();
383+
384+
// Check disk cache in background thread
385+
Bitmap thumbnail = getBitmapFromDiskCache(imageKey);
386+
387+
// Not found in disk cache
388+
if (thumbnail == null) {
389+
int px = getThumbnailDimension();
390+
391+
// Download thumbnail from server
392+
if (mClient != null) {
393+
GetMethod get;
394+
try {
395+
String uri = getSpaceSpecialUri(spaceSpecial);
396+
Timber.d("URI: %s", uri);
397+
get = new GetMethod(new URL(uri));
398+
int status = mClient.executeHttpMethod(get);
399+
if (status == HttpConstants.HTTP_OK) {
400+
InputStream inputStream = get.getResponseBodyAsStream();
401+
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
402+
thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
403+
404+
// Handle PNG
405+
if (spaceSpecial.getFile().getMimeType().equalsIgnoreCase("image/png")) {
406+
thumbnail = handlePNG(thumbnail, px);
407+
}
408+
409+
// Add thumbnail to cache
410+
if (thumbnail != null) {
411+
addBitmapToCache(imageKey, thumbnail);
412+
}
413+
} else {
414+
mClient.exhaustResponse(get.getResponseBodyAsStream());
415+
}
416+
} catch (Exception e) {
417+
Timber.e(e);
418+
}
419+
}
420+
}
421+
422+
return thumbnail;
423+
424+
}
352425
}
353426

354427
public static boolean cancelPotentialThumbnailWork(Object file, ImageView imageView) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import com.owncloud.android.data.preferences.datasources.SharedPreferencesProvid
3838
import com.owncloud.android.data.preferences.datasources.implementation.OCSharedPreferencesProvider
3939
import com.owncloud.android.data.sharing.shares.datasources.LocalShareDataSource
4040
import com.owncloud.android.data.sharing.shares.datasources.implementation.OCLocalShareDataSource
41+
import com.owncloud.android.data.spaces.datasources.LocalSpacesDataSource
42+
import com.owncloud.android.data.spaces.datasources.implementation.OCLocalSpacesDataSource
4143
import com.owncloud.android.data.storage.LocalStorageProvider
4244
import com.owncloud.android.data.storage.ScopedStorageProvider
4345
import com.owncloud.android.data.transfers.datasources.LocalTransferDataSource
@@ -56,6 +58,7 @@ val localDataSourceModule = module {
5658
single { OwncloudDatabase.getDatabase(androidContext()).userDao() }
5759
single { OwncloudDatabase.getDatabase(androidContext()).folderBackUpDao() }
5860
single { OwncloudDatabase.getDatabase(androidContext()).transferDao() }
61+
single { OwncloudDatabase.getDatabase(androidContext()).spacesDao() }
5962

6063
single<SharedPreferencesProvider> { OCSharedPreferencesProvider(get()) }
6164
single<LocalStorageProvider> { ScopedStorageProvider(dataFolder, androidContext()) }
@@ -67,4 +70,5 @@ val localDataSourceModule = module {
6770
factory<LocalUserDataSource> { OCLocalUserDataSource(get()) }
6871
factory<FolderBackupLocalDataSource> { OCFolderBackupLocalDataSource(get()) }
6972
factory<LocalTransferDataSource> { OCLocalTransferDataSource(get()) }
73+
factory<LocalSpacesDataSource> { OCLocalSpacesDataSource(get()) }
7074
}

0 commit comments

Comments
 (0)