Skip to content

Commit

Permalink
Merge pull request #4334 from owncloud/feature/manual_removal_local_s…
Browse files Browse the repository at this point in the history
…torage

[FEATURE REQUEST] Manual removal of local storage
  • Loading branch information
Aitorbp committed Apr 5, 2024
2 parents 4b268fa + 762ab03 commit e1cf321
Show file tree
Hide file tree
Showing 19 changed files with 237 additions and 12 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ ownCloud admins and users.
* Change - Add new prefixes in commit messages of 3rd party contributors: [#4346](https://github.com/owncloud/android/pull/4346)
* Enhancement - Correct "Local only" option in remove dialog: [#3936](https://github.com/owncloud/android/issues/3936)
* Enhancement - Improvements in Manage Accounts view: [#4148](https://github.com/owncloud/android/issues/4148)
* Enhancement - New setting for manual removal of local storage: [#4174](https://github.com/owncloud/android/issues/4174)
* Enhancement - New setting for automatic removal of local files: [#4175](https://github.com/owncloud/android/issues/4175)
* Enhancement - Unit tests for repository classes - Part 1: [#4232](https://github.com/owncloud/android/issues/4232)
* Enhancement - Add a warning in http connections: [#4284](https://github.com/owncloud/android/issues/4284)
Expand Down Expand Up @@ -97,6 +98,14 @@ ownCloud admins and users.
https://github.com/owncloud/android/issues/4148
https://github.com/owncloud/android/pull/4330

* Enhancement - New setting for manual removal of local storage: [#4174](https://github.com/owncloud/android/issues/4174)

A new icon has been added in Manage Accounts view to delete manually local
files.

https://github.com/owncloud/android/issues/4174
https://github.com/owncloud/android/pull/4334

* Enhancement - New setting for automatic removal of local files: [#4175](https://github.com/owncloud/android/issues/4175)

A new setting has been created to delete automatically downloaded files, when
Expand Down
6 changes: 6 additions & 0 deletions changelog/unreleased/4334
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: New setting for manual removal of local storage

A new icon has been added in Manage Accounts view to delete manually local files.

https://github.com/owncloud/android/issues/4174
https://github.com/owncloud/android/pull/4334
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import com.owncloud.android.domain.webfinger.usecases.GetOwnCloudInstanceFromWeb
import com.owncloud.android.domain.webfinger.usecases.GetOwnCloudInstancesFromAuthenticatedWebFingerUseCase
import com.owncloud.android.usecases.accounts.RemoveAccountUseCase
import com.owncloud.android.usecases.files.FilterFileMenuOptionsUseCase
import com.owncloud.android.usecases.files.RemoveLocalFilesForAccountUseCase
import com.owncloud.android.usecases.files.RemoveLocallyFilesWithLastUsageOlderThanGivenTimeUseCase
import com.owncloud.android.usecases.synchronization.SynchronizeFileUseCase
import com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase
Expand Down Expand Up @@ -176,6 +177,7 @@ val useCaseModule = module {
factoryOf(::ManageDeepLinkUseCase)
factoryOf(::MoveFileUseCase)
factoryOf(::RemoveFileUseCase)
factoryOf(::RemoveLocalFilesForAccountUseCase)
factoryOf(::RemoveLocallyFilesWithLastUsageOlderThanGivenTimeUseCase)
factoryOf(::RenameFileUseCase)
factoryOf(::SaveConflictUseCase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ val viewModelModule = module {
viewModel { MigrationViewModel(MainApp.dataFolder, get(), get(), get(), get(), get(), get(), get()) }
viewModel { TransfersViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel { ReceiveExternalFilesViewModel(get(), get(), get()) }
viewModel { AccountsManagementViewModel(get()) }
viewModel { AccountsManagementViewModel(get(), get(), get()) }
viewModel { (accountName: String, showPersonalSpace: Boolean) ->
SpacesListViewModel(get(), get(), get(), get(), get(), accountName, showPersonalSpace)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
* ownCloud Android client application
*
* @author Javier Rodríguez Pérez
* @author Aitor Ballesteros Pavón
*
* Copyright (C) 2022 ownCloud GmbH.
* Copyright (C) 2024 ownCloud GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
Expand All @@ -28,13 +29,17 @@ import android.accounts.OperationCanceledException
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.owncloud.android.MainApp.Companion.accountType
import com.owncloud.android.MainApp.Companion.initDependencyInjection
import com.owncloud.android.R
import com.owncloud.android.extensions.collectLatestLifecycleFlow
import com.owncloud.android.extensions.showErrorInSnackbar
import com.owncloud.android.presentation.accounts.RemoveAccountDialogFragment.Companion.newInstance
import com.owncloud.android.presentation.authentication.AccountUtils
import com.owncloud.android.presentation.common.UIResult
import com.owncloud.android.ui.activity.FileActivity
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment
Expand Down Expand Up @@ -73,6 +78,8 @@ class AccountsManagementActivity : FileActivity(), AccountsManagementAdapter.Acc
displayShowTitleEnabled = true
)

subscribeToViewModels()

}

override fun onStart() {
Expand Down Expand Up @@ -196,6 +203,39 @@ class AccountsManagementActivity : FileActivity(), AccountsManagementAdapter.Acc

}

override fun cleanAccountLocalStorage(account: Account) {
val dialog = AlertDialog.Builder(this)
.setTitle(getString(R.string.clean_data_account_title))
.setIcon(R.drawable.ic_warning)
.setMessage(getString(R.string.clean_data_account_message))
.setPositiveButton(getString(R.string.clean_data_account_button_yes)) { dialog, _ ->
accountsManagementViewModel.cleanAccountLocalStorage(account.name)
dialog.dismiss()
}
.setNegativeButton(R.string.drawer_close) { dialog, _ ->
dialog.dismiss()
}
.create()
dialog.show()
}

private fun subscribeToViewModels() {
collectLatestLifecycleFlow(accountsManagementViewModel.cleanAccountLocalStorageFlow) { event ->
event?.peekContent()?.let { uiResult ->
when (uiResult) {
is UIResult.Loading -> showLoadingDialog(R.string.common_loading)
is UIResult.Success -> dismissLoadingDialog()
is UIResult.Error -> {
dismissLoadingDialog()
showErrorInSnackbar(R.string.common_error_unknown, uiResult.error)
Timber.e(uiResult.error)
}

}
}
}
}

override fun run(future: AccountManagerFuture<Boolean>) {
if (future.isDone) {
// Create new adapter with the remaining accounts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
* ownCloud Android client application
*
* @author Javier Rodríguez Pérez
* @author Aitor Ballesteros Pavón
*
* Copyright (C) 2022 ownCloud GmbH.
* Copyright (C) 2024 ownCloud GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
Expand Down Expand Up @@ -99,6 +100,11 @@ class AccountsManagementAdapter(private val accountListener: AccountAdapterListe
holder.binding.ticker.visibility = View.INVISIBLE
}

/// bind listener to clean local storage from account
holder.binding.cleanAccountLocalStorageButton.apply {
setImageResource(R.drawable.ic_clean_account)
setOnClickListener { accountListener.cleanAccountLocalStorage(account) }
}
/// bind listener to remove account
holder.binding.removeButton.apply {
setImageResource(R.drawable.ic_action_delete_grey)
Expand Down Expand Up @@ -156,6 +162,7 @@ class AccountsManagementAdapter(private val accountListener: AccountAdapterListe
*/
interface AccountAdapterListener {
fun removeAccount(account: Account)
fun cleanAccountLocalStorage(account: Account)
fun createAccount()
fun switchAccount(position: Int)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
* ownCloud Android client application
*
* @author Javier Rodríguez Pérez
* @author Aitor Ballesteros Pavón
*
* Copyright (C) 2022 ownCloud GmbH.
* Copyright (C) 2024 ownCloud GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
Expand All @@ -22,17 +23,39 @@ package com.owncloud.android.presentation.accounts

import android.accounts.Account
import androidx.lifecycle.ViewModel
import com.owncloud.android.domain.utils.Event
import com.owncloud.android.extensions.ViewModelExt.runUseCaseWithResult
import com.owncloud.android.presentation.common.UIResult
import com.owncloud.android.providers.AccountProvider
import com.owncloud.android.providers.CoroutinesDispatcherProvider
import com.owncloud.android.usecases.files.RemoveLocalFilesForAccountUseCase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

class AccountsManagementViewModel(
private val accountProvider: AccountProvider
private val accountProvider: AccountProvider,
private val removeLocalFilesForAccountUseCase: RemoveLocalFilesForAccountUseCase,
private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider,
) : ViewModel() {

private val _cleanAccountLocalStorageFlow = MutableStateFlow<Event<UIResult<Unit>>?>(null)
val cleanAccountLocalStorageFlow: StateFlow<Event<UIResult<Unit>>?> = _cleanAccountLocalStorageFlow

fun getLoggedAccounts(): Array<Account> {
return accountProvider.getLoggedAccounts()
}

fun getCurrentAccount(): Account? {
return accountProvider.getCurrentOwnCloudAccount()
}

fun cleanAccountLocalStorage(accountName: String) {
runUseCaseWithResult(
coroutineDispatcher = coroutinesDispatcherProvider.io,
showLoading = true,
flow = _cleanAccountLocalStorageFlow,
useCase = removeLocalFilesForAccountUseCase,
useCaseParams = RemoveLocalFilesForAccountUseCase.Params(accountName),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ class ReleaseNotesViewModel(
subtitle = R.string.release_notes_4_3_0_subtitle_retried_successful_uploads_delete_temporary_folder,
type = ReleaseNoteType.BUGFIX,
),
ReleaseNote(
title = R.string.release_notes_4_3_0_title_manual_removal_local_storage,
subtitle = R.string.release_notes_4_3_0_subtitle_manual_removal_local_storage,
type = ReleaseNoteType.ENHANCEMENT,
),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* ownCloud Android client application
*
* @author Aitor Ballesteros Pavón
*
* Copyright (C) 2024 ownCloud GmbH.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.owncloud.android.usecases.files

import com.owncloud.android.domain.BaseUseCaseWithResult
import com.owncloud.android.domain.files.FileRepository
import com.owncloud.android.domain.files.usecases.RemoveFileUseCase

class RemoveLocalFilesForAccountUseCase(
private val fileRepository: FileRepository,
private val removeFileUseCase: RemoveFileUseCase,
) : BaseUseCaseWithResult<Unit, RemoveLocalFilesForAccountUseCase.Params>() {

override fun run(params: Params) {
val listOfFilesToDelete = fileRepository.getDownloadedFilesForAccount(params.owner)
if (listOfFilesToDelete.isNotEmpty()) {
removeFileUseCase(RemoveFileUseCase.Params(listOfFilesToDelete, true))
}
}

data class Params(
val owner: String,
)
}
10 changes: 10 additions & 0 deletions owncloudApp/src/main/res/drawable/ic_clean_account.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M440,440L520,440L520,160Q520,143 508.5,131.5Q497,120 480,120Q463,120 451.5,131.5Q440,143 440,160L440,440ZM200,600L760,600L760,520Q760,520 760,520Q760,520 760,520L200,520Q200,520 200,520Q200,520 200,520L200,600ZM142,840L240,840L240,760Q240,743 251.5,731.5Q263,720 280,720Q297,720 308.5,731.5Q320,743 320,760L320,840L440,840L440,760Q440,743 451.5,731.5Q463,720 480,720Q497,720 508.5,731.5Q520,743 520,760L520,840L640,840L640,760Q640,743 651.5,731.5Q663,720 680,720Q697,720 708.5,731.5Q720,743 720,760L720,840L818,840Q818,840 818,840Q818,840 818,840L778,680L182,680L142,840Q142,840 142,840Q142,840 142,840ZM818,920L142,920Q103,920 79,889Q55,858 65,820L120,600L120,520Q120,487 143.5,463.5Q167,440 200,440L360,440L360,160Q360,110 395,75Q430,40 480,40Q530,40 565,75Q600,110 600,160L600,440L760,440Q793,440 816.5,463.5Q840,487 840,520L840,600L895,820Q908,858 883.5,889Q859,920 818,920ZM760,520L200,520L200,520Q200,520 200,520Q200,520 200,520L760,520Q760,520 760,520Q760,520 760,520L760,520ZM520,440L440,440L440,440Q440,440 451.5,440Q463,440 480,440Q497,440 508.5,440Q520,440 520,440L520,440Z" />
</vector>
19 changes: 18 additions & 1 deletion owncloudApp/src/main/res/layout/account_item.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,27 @@
android:textColor="@color/textColor"
android:textSize="14sp"
android:lines="1"
app:layout_constraintEnd_toStartOf="@+id/removeButton"
app:layout_constraintEnd_toStartOf="@+id/clean_account_local_storage_button"
app:layout_constraintStart_toEndOf="@id/ticker"
app:layout_constraintTop_toBottomOf="@id/name" />

<ImageView
android:id="@+id/clean_account_local_storage_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="@dimen/standard_half_padding"
android:paddingTop="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_half_padding"
android:paddingBottom="@dimen/standard_padding"
android:src="@drawable/ic_clean_account"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/removeButton"
app:layout_constraintTop_toTopOf="parent"
app:tint="@color/black" />

<ImageView
android:id="@+id/removeButton"
android:layout_width="wrap_content"
Expand Down
6 changes: 6 additions & 0 deletions owncloudApp/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,8 @@
<string name="release_notes_4_3_0_subtitle_7">\"Local only\" option in remove dialog will only be shown if checking selected files and folders recursively, at least one file is available locally</string>
<string name="release_notes_4_3_0_title_retried_successful_uploads_delete_temporary_folder">Retried successful uploads are cleaned up from the temporary folder</string>
<string name="release_notes_4_3_0_subtitle_retried_successful_uploads_delete_temporary_folder">Temporary files related to an upload are deleted after the retried successful uploads is finished</string>
<string name="release_notes_4_3_0_title_manual_removal_local_storage">New setting to removal manually local storage</string>
<string name="release_notes_4_3_0_subtitle_manual_removal_local_storage">A new icon has been added in Manage Accounts view to delete manually local and temporary files</string>

<!-- Open in web -->
<string name="ic_action_open_in_web">Open in web</string>
Expand Down Expand Up @@ -793,4 +795,8 @@
<string name="insecure_http_url_message_dialog">The URL you provided uses HTTP rather than the encrypted HTTPS protocol. If you continue, your communication will not be encrypted.</string>
<string name="insecure_http_url_continue_button">Continue</string>

<string name="clean_data_account_message">All downloaded files that are not available offline will be cleaned up for this account</string>
<string name="clean_data_account_title">Remove local storage</string>
<string name="clean_data_account_button_yes">Clean data</string>

</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ interface LocalFileDataSource {
fun getFilesWithSyncInfoAvailableOfflineFromAccountAsFlow(owner: String): Flow<List<OCFileWithSyncInfo>>
fun getFilesAvailableOfflineFromAccount(owner: String): List<OCFile>
fun getFilesAvailableOfflineFromEveryAccount(): List<OCFile>
fun getDownloadedFilesForAccount(owner: String): List<OCFile>
fun getFileWithSyncInfoByIdAsFlow(id: Long): Flow<OCFileWithSyncInfo?>
fun getFilesWithLastUsageOlderThanGivenTime(milliseconds: Long): List<OCFile>
fun moveFile(sourceFile: OCFile, targetFolder: OCFile, finalRemotePath: String, finalStoragePath: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ class OCLocalFileDataSource(
it.toModel()
}

override fun getDownloadedFilesForAccount(owner: String): List<OCFile> =
fileDao.getDownloadedFilesForAccount(accountOwner = owner).map {
it.toModel()
}

override fun getFilesWithLastUsageOlderThanGivenTime(milliseconds: Long): List<OCFile> =
fileDao.getFilesWithLastUsageOlderThanGivenTime(milliseconds).map {
it.toModel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ interface FileDao {
@Query(SELECT_FILES_AVAILABLE_OFFLINE_FROM_EVERY_ACCOUNT)
fun getFilesAvailableOfflineFromEveryAccount(): List<OCFileEntity>

@Query(SELECT_DOWNLOADED_FILES_FOR_ACCOUNT)
fun getDownloadedFilesForAccount(
accountOwner: String
): List<OCFileEntity>

@Query(SELECT_FILES_WHERE_LAST_USAGE_IS_OLDER_THAN_GIVEN_TIME)
fun getFilesWithLastUsageOlderThanGivenTime(milliseconds: Long): List<OCFileEntity>

Expand Down Expand Up @@ -538,6 +543,12 @@ interface FileDao {
WHERE parentId = :folderId AND mimeType LIKE :mimeType || '%'
"""

private const val SELECT_DOWNLOADED_FILES_FOR_ACCOUNT = """
SELECT *
FROM ${ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME}
WHERE owner = :accountOwner AND storagePath IS NOT NULL AND keepInSync = '0'
"""

private const val SELECT_FILES_SHARED_BY_LINK = """
SELECT *
FROM ${ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ class OCFileRepository(
override fun getFilesAvailableOfflineFromEveryAccount(): List<OCFile> =
localFileDataSource.getFilesAvailableOfflineFromEveryAccount()

override fun getDownloadedFilesForAccount(owner: String): List<OCFile> = localFileDataSource.getDownloadedFilesForAccount(owner)

override fun getFilesWithLastUsageOlderThanGivenTime(milliseconds: Long): List<OCFile> =
localFileDataSource.getFilesWithLastUsageOlderThanGivenTime(milliseconds)

Expand Down
Loading

0 comments on commit e1cf321

Please sign in to comment.