From e80b909d93bf772ffc06e2ed4701ec744831a239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 08:04:03 +0200 Subject: [PATCH 01/13] Update library reference --- owncloud-android-library | 2 +- .../com/owncloud/android/operations/CopyFileOperation.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/owncloud-android-library b/owncloud-android-library index 4631f8e4b06..fd41310f226 160000 --- a/owncloud-android-library +++ b/owncloud-android-library @@ -1 +1 @@ -Subproject commit 4631f8e4b06b9d32f1fa46b3d31fed3e3ba23d39 +Subproject commit fd41310f22651f78ea468b8cf2c004ab9850d5af diff --git a/owncloudApp/src/main/java/com/owncloud/android/operations/CopyFileOperation.java b/owncloudApp/src/main/java/com/owncloud/android/operations/CopyFileOperation.java index 3a342bdc89a..c4d56d11b44 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/operations/CopyFileOperation.java +++ b/owncloudApp/src/main/java/com/owncloud/android/operations/CopyFileOperation.java @@ -87,8 +87,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { } CopyRemoteFileOperation operation = new CopyRemoteFileOperation( mSrcPath, - finalRemotePath, - false + finalRemotePath ); result = operation.execute(client); String targetFileRemoteId = (String) result.getData(); From 50dcca4f64a1d91f32ea3296f0d75d97cdba3fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 09:02:20 +0200 Subject: [PATCH 02/13] Create copy file usecase --- .../dependecyinjection/UseCaseModule.kt | 2 + .../data/files/repository/OCFileRepository.kt | 4 ++ .../android/domain/files/FileRepository.kt | 1 + .../domain/files/usecases/CopyFileUseCase.kt | 50 +++++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/CopyFileUseCase.kt diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index bd56fa00008..7a5e1bdc35f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -29,6 +29,7 @@ import com.owncloud.android.domain.authentication.usecases.SupportsOAuth2UseCase import com.owncloud.android.domain.capabilities.usecases.GetCapabilitiesAsLiveDataUseCase import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase import com.owncloud.android.domain.capabilities.usecases.RefreshCapabilitiesFromServerAsyncUseCase +import com.owncloud.android.domain.files.usecases.CopyFileUseCase import com.owncloud.android.domain.files.usecases.CreateFolderAsyncUseCase import com.owncloud.android.domain.files.usecases.GetFileByIdUseCase import com.owncloud.android.domain.files.usecases.GetFileByRemotePathUseCase @@ -79,6 +80,7 @@ val useCaseModule = module { // Files factory { CreateFolderAsyncUseCase(get()) } + factory { CopyFileUseCase(get()) } factory { GetFileByIdUseCase(get()) } factory { GetFileByRemotePathUseCase(get()) } factory { GetFolderContentUseCase(get()) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index f2b19613fa4..1eb85a19c31 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -62,6 +62,10 @@ class OCFileRepository( } } + override fun copyFile(listOfFilesToCopy: List, targetFolder: OCFile) { + TODO("Not yet implemented") + } + override fun getFileById(fileId: Long): OCFile? = localFileDataSource.getFileById(fileId) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt index c015a8c76d1..806f452dab0 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt @@ -24,6 +24,7 @@ import com.owncloud.android.domain.files.model.OCFile interface FileRepository { fun createFolder(remotePath: String, parentFolder: OCFile) + fun copyFile(listOfFilesToCopy: List, targetFolder: OCFile) fun getFileById(fileId: Long): OCFile? fun getFileByRemotePath(remotePath: String, owner: String): OCFile? fun getFolderContent(folderId: Long): List diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/CopyFileUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/CopyFileUseCase.kt new file mode 100644 index 00000000000..ef3661bb474 --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/CopyFileUseCase.kt @@ -0,0 +1,50 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2021 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 . + */ +package com.owncloud.android.domain.files.usecases + +import com.owncloud.android.domain.BaseUseCaseWithResult +import com.owncloud.android.domain.exceptions.CopyIntoDescendantException +import com.owncloud.android.domain.exceptions.MoveIntoDescendantException +import com.owncloud.android.domain.files.FileRepository +import com.owncloud.android.domain.files.model.OCFile + +class CopyFileUseCase( + private val fileRepository: FileRepository +) : BaseUseCaseWithResult() { + + override fun run(params: Params) { + + require(params.listOfFilesToCopy.isNotEmpty()) + + val listWithoutDescendantItems = params.listOfFilesToCopy.dropWhile { + params.targetFolder.remotePath.startsWith(it.remotePath) + } + if (listWithoutDescendantItems.isEmpty()) throw CopyIntoDescendantException() + + return fileRepository.moveFile( + listOfFilesToMove = listWithoutDescendantItems, + targetFile = params.targetFolder + ) + } + + data class Params( + val listOfFilesToCopy: List, + val targetFolder: OCFile + ) +} From 48919379539cea55039bc8357dc0f20aa51345bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 09:09:21 +0200 Subject: [PATCH 03/13] Add copy file to file repository --- .../android/data/LocalStorageProvider.kt | 4 ++ .../files/datasources/LocalFileDataSource.kt | 1 + .../files/datasources/RemoteFileDataSource.kt | 2 + .../implementation/OCLocalFileDataSource.kt | 3 ++ .../implementation/OCRemoteFileDataSource.kt | 4 ++ .../data/files/repository/OCFileRepository.kt | 46 ++++++++++++++++++- 6 files changed, 59 insertions(+), 1 deletion(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/LocalStorageProvider.kt b/owncloudData/src/main/java/com/owncloud/android/data/LocalStorageProvider.kt index 880c0f15b15..d1d84d25bab 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/LocalStorageProvider.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/LocalStorageProvider.kt @@ -102,6 +102,10 @@ class LocalStorageProvider( return fileToDelete.deleteRecursively() } + fun copyLocalFile(ocFile: OCFile, finalStoragePath: String) { + TODO("Not yet implemented") + } + fun moveLocalFile(ocFile: OCFile, finalStoragePath: String) { val safeStoragePath = ocFile.storagePath ?: getDefaultSavePathFor(accountName = ocFile.owner, remotePath = ocFile.remotePath) val fileToMove = File(safeStoragePath) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt index 93b8d1e1e5b..d34e2983b96 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt @@ -23,6 +23,7 @@ package com.owncloud.android.data.files.datasources import com.owncloud.android.domain.files.model.OCFile interface LocalFileDataSource { + fun copyFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, finalStoragePath: String) fun getFileById(fileId: Long): OCFile? fun getFileByRemotePath(remotePath: String, owner: String): OCFile? fun getFolderContent(folderId: Long): List diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt index 7fe4e283de0..5e9fb74a5be 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt @@ -27,6 +27,8 @@ interface RemoteFileDataSource { checkUserCredentials: Boolean ): Boolean + fun copyFile(sourceRemotePath: String, targetRemotePath: String) + fun createFolder( remotePath: String, createFullPath: Boolean, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt index f217b25cd74..0de8d801e9f 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt @@ -31,6 +31,9 @@ import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH class OCLocalFileDataSource( private val fileDao: FileDao, ) : LocalFileDataSource { + override fun copyFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, finalStoragePath: String) { + TODO("Not yet implemented") + } override fun getFileById(fileId: Long): OCFile? = fileDao.getFileById(fileId)?.toModel() diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt index cb7b87d9a9c..5fca99c18d5 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt @@ -38,6 +38,10 @@ class OCRemoteFileDataSource( isUserLogged = checkUserCredentials ).data + override fun copyFile(sourceRemotePath: String, targetRemotePath: String) { + TODO("Not yet implemented") + } + override fun createFolder( remotePath: String, createFullPath: Boolean, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 1eb85a19c31..1e7ea0fa835 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -63,7 +63,51 @@ class OCFileRepository( } override fun copyFile(listOfFilesToCopy: List, targetFolder: OCFile) { - TODO("Not yet implemented") + listOfFilesToCopy.forEach { ocFile -> + + // 1. Get the final remote path for this file. + val expectedRemotePath: String = targetFolder.remotePath + ocFile.fileName + val finalRemotePath: String = remoteFileDataSource.getAvailableRemotePath(expectedRemotePath).apply { + if (ocFile.isFolder) { + plus(File.separator) + } + } + val finalStoragePath: String = localStorageProvider.getDefaultSavePathFor(targetFolder.owner, finalRemotePath) + + // 2. Try to copy files in server + try { + remoteFileDataSource.copyFile( + sourceRemotePath = ocFile.remotePath, + targetRemotePath = finalRemotePath + ) + } catch (targetNodeDoesNotExist: ConflictException) { + // Target node does not exist anymore. Remove target folder from database and local storage and return + removeFolderRecursively(ocFile = targetFolder, removeOnlyLocalCopy = false) + return@copyFile + } catch (sourceFileDoesNotExist: FileNotFoundException) { + // Source file does not exist anymore. Remove file from database and local storage and continue + if (ocFile.isFolder) { + removeFolderRecursively(ocFile = ocFile, removeOnlyLocalCopy = false) + } else { + removeFile( + ocFile = ocFile, + onlyLocalCopy = false + ) + } + return@forEach + } + + // 3. Update database with latest changes + localFileDataSource.copyFile( + sourceFile = ocFile, + targetFile = targetFolder, + finalRemotePath = finalRemotePath, + finalStoragePath = finalStoragePath + ) + + // 4. Update local storage + localStorageProvider.copyLocalFile(ocFile, finalStoragePath) + } } override fun getFileById(fileId: Long): OCFile? = From dcf9e946a214ea382cd1cb2fd9a8c824757e6608 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 09:11:57 +0200 Subject: [PATCH 04/13] Implement copy operation in remote data source --- owncloud-android-library | 2 +- .../data/files/datasources/RemoteFileDataSource.kt | 2 +- .../implementation/OCRemoteFileDataSource.kt | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/owncloud-android-library b/owncloud-android-library index fd41310f226..48a6526f506 160000 --- a/owncloud-android-library +++ b/owncloud-android-library @@ -1 +1 @@ -Subproject commit fd41310f22651f78ea468b8cf2c004ab9850d5af +Subproject commit 48a6526f506c29594c2e7cc201a4fc42c6a19cf2 diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt index 5e9fb74a5be..864601a8afd 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/RemoteFileDataSource.kt @@ -27,7 +27,7 @@ interface RemoteFileDataSource { checkUserCredentials: Boolean ): Boolean - fun copyFile(sourceRemotePath: String, targetRemotePath: String) + fun copyFile(sourceRemotePath: String, targetRemotePath: String): String fun createFolder( remotePath: String, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt index 5fca99c18d5..d80661aea88 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCRemoteFileDataSource.kt @@ -38,8 +38,14 @@ class OCRemoteFileDataSource( isUserLogged = checkUserCredentials ).data - override fun copyFile(sourceRemotePath: String, targetRemotePath: String) { - TODO("Not yet implemented") + override fun copyFile( + sourceRemotePath: String, + targetRemotePath: String + ): String = executeRemoteOperation { + clientManager.getFileService().copyFile( + sourceRemotePath = sourceRemotePath, + targetRemotePath = targetRemotePath + ) } override fun createFolder( From f47141eec5116a86e1ac12f289dba5a812362432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 09:52:18 +0200 Subject: [PATCH 05/13] Copy files in the database --- .../android/data/LocalStorageProvider.kt | 4 - .../files/datasources/LocalFileDataSource.kt | 2 +- .../implementation/OCLocalFileDataSource.kt | 9 +- .../owncloud/android/data/files/db/FileDao.kt | 97 +++++++++++++++++++ .../data/files/repository/OCFileRepository.kt | 8 +- 5 files changed, 107 insertions(+), 13 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/LocalStorageProvider.kt b/owncloudData/src/main/java/com/owncloud/android/data/LocalStorageProvider.kt index d1d84d25bab..880c0f15b15 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/LocalStorageProvider.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/LocalStorageProvider.kt @@ -102,10 +102,6 @@ class LocalStorageProvider( return fileToDelete.deleteRecursively() } - fun copyLocalFile(ocFile: OCFile, finalStoragePath: String) { - TODO("Not yet implemented") - } - fun moveLocalFile(ocFile: OCFile, finalStoragePath: String) { val safeStoragePath = ocFile.storagePath ?: getDefaultSavePathFor(accountName = ocFile.owner, remotePath = ocFile.remotePath) val fileToMove = File(safeStoragePath) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt index d34e2983b96..92614e96774 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt @@ -23,7 +23,7 @@ package com.owncloud.android.data.files.datasources import com.owncloud.android.domain.files.model.OCFile interface LocalFileDataSource { - fun copyFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, finalStoragePath: String) + fun copyFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, remoteId: String) fun getFileById(fileId: Long): OCFile? fun getFileByRemotePath(remotePath: String, owner: String): OCFile? fun getFolderContent(folderId: Long): List diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt index 0de8d801e9f..b14a99bd24f 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt @@ -31,8 +31,13 @@ import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH class OCLocalFileDataSource( private val fileDao: FileDao, ) : LocalFileDataSource { - override fun copyFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, finalStoragePath: String) { - TODO("Not yet implemented") + override fun copyFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, remoteId: String) { + fileDao.copy( + sourceFile = sourceFile.toEntity(), + targetFile = targetFile.toEntity(), + finalRemotePath = finalRemotePath, + remoteId = remoteId + ) } override fun getFileById(fileId: Long): OCFile? = diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt index 3f0478ba97b..894db322108 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt @@ -73,6 +73,38 @@ abstract class FileDao { } } + @Transaction + open fun copy( + sourceFile: OCFileEntity, + targetFile: OCFileEntity, + finalRemotePath: String, + remoteId: String? + ) { + // 1. Update target size + insert( + targetFile.copy( + length = targetFile.length + sourceFile.length + ).apply { id = targetFile.id } + ) + + // 2. Duplicate files + if (sourceFile.isFolder) { + copyFolder( + sourceFolder = sourceFile, + targetFile = targetFile, + targetRemotePath = finalRemotePath, + remoteId = remoteId + ) + } else { + copySingleFile( + sourceFile = sourceFile, + targetFile = targetFile, + finalRemotePath = finalRemotePath, + remoteId = remoteId + ) + } + } + @Transaction open fun moveFile( sourceFile: OCFileEntity, @@ -110,6 +142,71 @@ abstract class FileDao { @Query(DELETE_FILE_WITH_ID) abstract fun deleteFileWithId(id: Long) + private fun copySingleFile( + sourceFile: OCFileEntity, + targetFile: OCFileEntity, + finalRemotePath: String, + remoteId: String? = null + ) { + insert( + OCFileEntity( + parentId = targetFile.id, + owner = targetFile.owner, + remotePath = finalRemotePath, + remoteId = remoteId, + length = sourceFile.length, + modificationTimestamp = sourceFile.modificationTimestamp, + mimeType = sourceFile.mimeType, + name = null, + needsToUpdateThumbnail = true, + etag = null, + creationTimestamp = null, + permissions = null + ) + ) + } + + private fun copyFolder( + sourceFolder: OCFileEntity, + targetFile: OCFileEntity, + targetRemotePath: String, + remoteId: String? + ) { + // 1. Move the folder + val folderRemotePath = + targetRemotePath.trimEnd(separatorChar).plus(separatorChar) + + copySingleFile( + sourceFile = sourceFolder, + targetFile = targetFile, + finalRemotePath = folderRemotePath, + remoteId = remoteId + ) + + // 2. Move its content + val folderContent = getFolderContent(sourceFolder.id) + + folderContent.forEach { file -> + val remotePathForChild = folderRemotePath.plus(file.name) + + if (file.isFolder) { + copyFolder( + sourceFolder = file, + targetFile = sourceFolder, + targetRemotePath = remotePathForChild, + remoteId = null + ) + } else { + copySingleFile( + sourceFile = file, + targetFile = sourceFolder, + finalRemotePath = remotePathForChild, + remoteId = remoteId + ) + } + } + } + private fun moveSingleFile( sourceFile: OCFileEntity, targetFile: OCFileEntity, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 1e7ea0fa835..8886b875dcd 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -72,10 +72,9 @@ class OCFileRepository( plus(File.separator) } } - val finalStoragePath: String = localStorageProvider.getDefaultSavePathFor(targetFolder.owner, finalRemotePath) // 2. Try to copy files in server - try { + val remoteId = try { remoteFileDataSource.copyFile( sourceRemotePath = ocFile.remotePath, targetRemotePath = finalRemotePath @@ -102,11 +101,8 @@ class OCFileRepository( sourceFile = ocFile, targetFile = targetFolder, finalRemotePath = finalRemotePath, - finalStoragePath = finalStoragePath + remoteId = remoteId ) - - // 4. Update local storage - localStorageProvider.copyLocalFile(ocFile, finalStoragePath) } } From 637a20d070d65e25901aeb19314e86d0b59bfe96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 10:01:01 +0200 Subject: [PATCH 06/13] Add copy operation to the viewmodel --- .../android/dependecyinjection/ViewModelModule.kt | 2 +- .../ui/files/operations/FileOperation.kt | 1 + .../ui/files/operations/FileOperationViewModel.kt | 15 +++++++++++++++ .../android/ui/activity/FileDisplayActivity.kt | 5 +++-- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index bc167fe6876..4034e9d4b79 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -64,5 +64,5 @@ val viewModelModule = module { viewModel { PreviewImageViewModel(get(), get(), get()) } viewModel { FileDetailsViewModel(get(), get(), get(), get(), get()) } - viewModel { FileOperationViewModel(get(), get(), get(), get(), get()) } + viewModel { FileOperationViewModel(get(), get(), get(), get(), get(), get()) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperation.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperation.kt index ed0a0fa6d7a..0122d86221f 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperation.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperation.kt @@ -22,6 +22,7 @@ package com.owncloud.android.presentation.ui.files.operations import com.owncloud.android.domain.files.model.OCFile sealed class FileOperation { + data class CopyOperation(val listOfFilesToCopy: List, val targetFolder: OCFile) : FileOperation() data class MoveOperation(val listOfFilesToMove: List, val targetFolder: OCFile) : FileOperation() data class RemoveOperation(val listOfFilesToRemove: List, val removeOnlyLocalCopy: Boolean) : FileOperation() data class RenameOperation(val ocFileToRename: OCFile, val newName: String) : FileOperation() diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperationViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperationViewModel.kt index 18e527a03f7..eb18dfae826 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperationViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperationViewModel.kt @@ -27,6 +27,7 @@ import com.owncloud.android.domain.BaseUseCaseWithResult import com.owncloud.android.domain.UseCaseResult import com.owncloud.android.domain.exceptions.NoNetworkConnectionException import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.domain.files.usecases.CopyFileUseCase import com.owncloud.android.domain.files.usecases.MoveFileUseCase import com.owncloud.android.domain.files.usecases.RemoveFileUseCase import com.owncloud.android.domain.files.usecases.RenameFileUseCase @@ -38,6 +39,7 @@ import kotlinx.coroutines.launch import timber.log.Timber class FileOperationViewModel( + private val copyFileUseCase: CopyFileUseCase, private val moveFileUseCase: MoveFileUseCase, private val removeFileUseCase: RemoveFileUseCase, private val renameFileUseCase: RenameFileUseCase, @@ -45,6 +47,9 @@ class FileOperationViewModel( private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider ) : ViewModel() { + private val _copyFileLiveData = MediatorLiveData>>() + val copyFileLiveData: LiveData>> = _copyFileLiveData + private val _moveFileLiveData = MediatorLiveData>>() val moveFileLiveData: LiveData>> = _moveFileLiveData @@ -59,9 +64,19 @@ class FileOperationViewModel( is FileOperation.MoveOperation -> moveOperation(fileOperation) is FileOperation.RemoveOperation -> removeOperation(fileOperation) is FileOperation.RenameOperation -> renameOperation(fileOperation) + is FileOperation.CopyOperation -> copyOperation(fileOperation) } } + private fun copyOperation(fileOperation: FileOperation.CopyOperation) { + runOperation( + liveData = _copyFileLiveData, + useCase = copyFileUseCase, + useCaseParams = CopyFileUseCase.Params(fileOperation.listOfFilesToCopy, fileOperation.targetFolder), + postValue = fileOperation.targetFolder + ) + } + private fun moveOperation(fileOperation: FileOperation.MoveOperation) { runOperation( liveData = _moveFileLiveData, diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 130a69f5f33..cebf34509da 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -646,9 +646,10 @@ class FileDisplayActivity : FileActivity(), FileFragment.ContainerActivity, OnEn * @param data Intent received */ private fun requestCopyOperation(data: Intent) { - val folderToMoveAt = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER) + val folderToCopyAt = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER) val files = data.getParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES) - fileOperationsHelper.copyFiles(files, folderToMoveAt) + val copyOperation = FileOperation.CopyOperation(listOfFilesToCopy = files.toList(), targetFolder = folderToCopyAt) + fileOperationViewModel.performOperation(copyOperation) } override fun onBackPressed() { From 4a9cd8f7deea66fa9a498d28bf57949836560ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 10:22:03 +0200 Subject: [PATCH 07/13] Fix a typo :S --- .../android/domain/files/usecases/CopyFileUseCase.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/CopyFileUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/CopyFileUseCase.kt index ef3661bb474..1b2c1328486 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/CopyFileUseCase.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/CopyFileUseCase.kt @@ -20,7 +20,6 @@ package com.owncloud.android.domain.files.usecases import com.owncloud.android.domain.BaseUseCaseWithResult import com.owncloud.android.domain.exceptions.CopyIntoDescendantException -import com.owncloud.android.domain.exceptions.MoveIntoDescendantException import com.owncloud.android.domain.files.FileRepository import com.owncloud.android.domain.files.model.OCFile @@ -37,9 +36,9 @@ class CopyFileUseCase( } if (listWithoutDescendantItems.isEmpty()) throw CopyIntoDescendantException() - return fileRepository.moveFile( - listOfFilesToMove = listWithoutDescendantItems, - targetFile = params.targetFolder + return fileRepository.copyFile( + listOfFilesToCopy = listWithoutDescendantItems, + targetFolder = params.targetFolder ) } From b5b6478c311422196f3177c9129f04905d81168d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 14:00:40 +0200 Subject: [PATCH 08/13] Error handling for copy operation --- .../android/extensions/ThrowableExt.kt | 2 ++ .../ui/activity/FileDisplayActivity.kt | 34 ++++++++++--------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/extensions/ThrowableExt.kt b/owncloudApp/src/main/java/com/owncloud/android/extensions/ThrowableExt.kt index f2d14d97111..07560b5372a 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/extensions/ThrowableExt.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/extensions/ThrowableExt.kt @@ -24,6 +24,7 @@ import com.owncloud.android.R import com.owncloud.android.domain.exceptions.AccountNotNewException import com.owncloud.android.domain.exceptions.AccountNotTheSameException import com.owncloud.android.domain.exceptions.BadOcVersionException +import com.owncloud.android.domain.exceptions.CopyIntoDescendantException import com.owncloud.android.domain.exceptions.FileNotFoundException import com.owncloud.android.domain.exceptions.ForbiddenException import com.owncloud.android.domain.exceptions.IncorrectAddressException @@ -72,6 +73,7 @@ fun Throwable.parseError( resources.getString(stringId) } is MoveIntoDescendantException -> resources.getString(R.string.move_file_invalid_into_descendent) + is CopyIntoDescendantException -> resources.getString(R.string.copy_file_invalid_into_descendent) is ForbiddenException -> resources.getString(R.string.forbidden_permissions) is FileNotFoundException -> resources.getString(R.string.common_not_found) is InstanceNotConfiguredException -> resources.getString(R.string.auth_not_configured_title) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index cebf34509da..55088bd4a41 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -39,7 +39,6 @@ import android.content.Intent import android.content.IntentFilter import android.content.ServiceConnection import android.content.pm.PackageManager -import android.content.res.Resources.NotFoundException import android.net.Uri import android.os.Build import android.os.Bundle @@ -75,7 +74,6 @@ import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode import com.owncloud.android.lib.resources.status.OwnCloudVersion -import com.owncloud.android.operations.CopyFileOperation import com.owncloud.android.operations.RefreshFolderOperation import com.owncloud.android.operations.SynchronizeFileOperation import com.owncloud.android.operations.UploadFileOperation @@ -1183,7 +1181,6 @@ class FileDisplayActivity : FileActivity(), FileFragment.ContainerActivity, OnEn when (operation) { is SynchronizeFileOperation -> onSynchronizeFileOperationFinish(operation, result) - is CopyFileOperation -> onCopyFileOperationFinish(operation, result) } } @@ -1268,23 +1265,25 @@ class FileDisplayActivity : FileActivity(), FileFragment.ContainerActivity, OnEn * Updates the view associated to the activity after the finish of an operation trying to copy a * file. * - * @param operation Copy operation performed. - * @param result Result of the copy operation. + * @param uiResult - UIResult wrapping the target folder where files were copied */ - private fun onCopyFileOperationFinish(operation: CopyFileOperation, result: RemoteOperationResult<*>) { - if (result.isSuccess) { - refreshListOfFilesFragment(true) - } else { - try { - showMessageInSnackbar( - R.id.list_layout, - ErrorMessageAdapter.getResultMessage(result, operation, resources) - ) + private fun onCopyFileOperationFinish( + uiResult: UIResult + ) { + when (uiResult) { + is UIResult.Loading -> { + showLoadingDialog(R.string.wait_a_moment) + } + is UIResult.Success -> { + dismissLoadingDialog() - } catch (e: NotFoundException) { - Timber.e(e, "Error while trying to show fail message") + refreshListOfFilesFragment(true) } + is UIResult.Error -> { + dismissLoadingDialog() + showErrorInSnackbar(R.string.copy_file_error, uiResult.error) + } } } @@ -1631,6 +1630,9 @@ class FileDisplayActivity : FileActivity(), FileFragment.ContainerActivity, OnEn } private fun startListeningToOperations() { + fileOperationViewModel.copyFileLiveData.observe(this, Event.EventObserver { + onCopyFileOperationFinish(it) + }) fileOperationViewModel.moveFileLiveData.observe(this, Event.EventObserver { onMoveFileOperationFinish(it) }) From 97db9a4058f960d7c5c89bbfc1aae79358823086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 16:07:22 +0200 Subject: [PATCH 09/13] Simplify copy file in database --- .../owncloud/android/data/files/db/FileDao.kt | 97 ++++--------------- .../data/files/repository/OCFileRepository.kt | 12 +-- 2 files changed, 21 insertions(+), 88 deletions(-) diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt index 894db322108..266d6c40571 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt @@ -87,22 +87,24 @@ abstract class FileDao { ).apply { id = targetFile.id } ) - // 2. Duplicate files - if (sourceFile.isFolder) { - copyFolder( - sourceFolder = sourceFile, - targetFile = targetFile, - targetRemotePath = finalRemotePath, - remoteId = remoteId - ) - } else { - copySingleFile( - sourceFile = sourceFile, - targetFile = targetFile, - finalRemotePath = finalRemotePath, - remoteId = remoteId + // 2. Insert a new file with common attributes and retrieved remote id + insert( + OCFileEntity( + parentId = targetFile.id, + owner = targetFile.owner, + remotePath = finalRemotePath, + remoteId = remoteId, + length = sourceFile.length, + modificationTimestamp = sourceFile.modificationTimestamp, + mimeType = sourceFile.mimeType, + name = null, + needsToUpdateThumbnail = true, + etag = "", + creationTimestamp = null, + permissions = null, + treeEtag = "" ) - } + ) } @Transaction @@ -142,71 +144,6 @@ abstract class FileDao { @Query(DELETE_FILE_WITH_ID) abstract fun deleteFileWithId(id: Long) - private fun copySingleFile( - sourceFile: OCFileEntity, - targetFile: OCFileEntity, - finalRemotePath: String, - remoteId: String? = null - ) { - insert( - OCFileEntity( - parentId = targetFile.id, - owner = targetFile.owner, - remotePath = finalRemotePath, - remoteId = remoteId, - length = sourceFile.length, - modificationTimestamp = sourceFile.modificationTimestamp, - mimeType = sourceFile.mimeType, - name = null, - needsToUpdateThumbnail = true, - etag = null, - creationTimestamp = null, - permissions = null - ) - ) - } - - private fun copyFolder( - sourceFolder: OCFileEntity, - targetFile: OCFileEntity, - targetRemotePath: String, - remoteId: String? - ) { - // 1. Move the folder - val folderRemotePath = - targetRemotePath.trimEnd(separatorChar).plus(separatorChar) - - copySingleFile( - sourceFile = sourceFolder, - targetFile = targetFile, - finalRemotePath = folderRemotePath, - remoteId = remoteId - ) - - // 2. Move its content - val folderContent = getFolderContent(sourceFolder.id) - - folderContent.forEach { file -> - val remotePathForChild = folderRemotePath.plus(file.name) - - if (file.isFolder) { - copyFolder( - sourceFolder = file, - targetFile = sourceFolder, - targetRemotePath = remotePathForChild, - remoteId = null - ) - } else { - copySingleFile( - sourceFile = file, - targetFile = sourceFolder, - finalRemotePath = remotePathForChild, - remoteId = remoteId - ) - } - } - } - private fun moveSingleFile( sourceFile: OCFileEntity, targetFile: OCFileEntity, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index 8886b875dcd..5c55dd24d5e 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -67,10 +67,8 @@ class OCFileRepository( // 1. Get the final remote path for this file. val expectedRemotePath: String = targetFolder.remotePath + ocFile.fileName - val finalRemotePath: String = remoteFileDataSource.getAvailableRemotePath(expectedRemotePath).apply { - if (ocFile.isFolder) { - plus(File.separator) - } + val finalRemotePath: String = remoteFileDataSource.getAvailableRemotePath(expectedRemotePath).let { + if (ocFile.isFolder) it.plus(File.separator) else it } // 2. Try to copy files in server @@ -123,10 +121,8 @@ class OCFileRepository( // 1. Get the final remote path for this file. val expectedRemotePath: String = targetFile.remotePath + ocFile.fileName - val finalRemotePath: String = remoteFileDataSource.getAvailableRemotePath(expectedRemotePath).apply { - if (ocFile.isFolder) { - plus(File.separator) - } + val finalRemotePath: String = remoteFileDataSource.getAvailableRemotePath(expectedRemotePath).let { + if (ocFile.isFolder) it.plus(File.separator) else it } val finalStoragePath: String = localStorageProvider.getDefaultSavePathFor(targetFile.owner, finalRemotePath) From ddd88e9ae341c634b3c69f8a6594fa6be049074e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 16:12:05 +0200 Subject: [PATCH 10/13] Implement copy operation in DocumentsProvider using the new use case --- .../providers/DocumentsStorageProvider.kt | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.kt index fbba5639b22..2fd0ce13f29 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.kt @@ -43,6 +43,7 @@ import com.owncloud.android.domain.UseCaseResult import com.owncloud.android.domain.exceptions.NoConnectionWithServerException import com.owncloud.android.domain.exceptions.validation.FileNameException import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.domain.files.usecases.CopyFileUseCase import com.owncloud.android.domain.files.usecases.CreateFolderAsyncUseCase import com.owncloud.android.domain.files.usecases.MoveFileUseCase import com.owncloud.android.domain.files.usecases.RemoveFileUseCase @@ -50,7 +51,6 @@ import com.owncloud.android.domain.files.usecases.RenameFileUseCase import com.owncloud.android.files.services.FileUploader import com.owncloud.android.files.services.TransferRequester import com.owncloud.android.lib.common.operations.RemoteOperationResult -import com.owncloud.android.operations.CopyFileOperation import com.owncloud.android.operations.RefreshFolderOperation import com.owncloud.android.operations.SynchronizeFileOperation import com.owncloud.android.operations.UploadFileOperation @@ -348,21 +348,21 @@ class DocumentsStorageProvider : DocumentsProvider() { val targetParentDocId = targetParentDocumentId.toLong() val targetParentFile = getFileByIdOrException(targetParentDocId) - CopyFileOperation( - sourceFile.remotePath, - targetParentFile.remotePath - ).apply { - execute(currentStorageManager, context).also { result -> - syncRequired = false - checkOperationResult(result, targetParentFile.id.toString()) - //Returns the document id of the document copied at the target destination - var newPath = targetParentFile.remotePath + sourceFile.fileName - if (sourceFile.isFolder) { - newPath += File.separator - } - val newFile = getFileByPathOrException(newPath) - return newFile.id.toString() - } + val copyFileUseCase: CopyFileUseCase by inject() + + copyFileUseCase.execute( + CopyFileUseCase.Params( + listOfFilesToCopy = listOf(sourceFile), + targetFolder = targetParentFile + ) + ).also { result -> + syncRequired = false + checkUseCaseResult(result, targetParentFile.id.toString()) + // Returns the document id of the document copied at the target destination + var newPath = targetParentFile.remotePath + sourceFile.fileName + if (sourceFile.isFolder) newPath += File.separator + val newFile = getFileByPathOrException(newPath) + return newFile.id.toString() } } @@ -389,7 +389,7 @@ class DocumentsStorageProvider : DocumentsProvider() { ).also { result -> syncRequired = false checkUseCaseResult(result, targetParentFile.id.toString()) - //Returns the document id of the document moved to the target destination + // Returns the document id of the document moved to the target destination var newPath = targetParentFile.remotePath + sourceFile.fileName if (sourceFile.isFolder) newPath += File.separator val newFile = getFileByPathOrException(newPath) From 2d59f2606674a958ead517fbf366eddf00c344a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 16:16:39 +0200 Subject: [PATCH 11/13] Remove legacy copy operation --- .../datamodel/FileDataStorageManager.kt | 71 ------------ .../android/operations/CopyFileOperation.java | 103 ------------------ .../android/services/OperationsService.java | 11 -- .../ui/errorhandling/ErrorMessageAdapter.kt | 10 +- .../ui/helpers/FileOperationsHelper.java | 18 --- 5 files changed, 3 insertions(+), 210 deletions(-) delete mode 100644 owncloudApp/src/main/java/com/owncloud/android/operations/CopyFileOperation.java diff --git a/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt b/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt index cc8475d4b70..38aa775a879 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt @@ -625,77 +625,6 @@ class FileDataStorageManager : KoinComponent { // return updatedCount > 0 } - fun copyLocalFile(originalFile: OCFile?, targetPath: String, targetFileRemoteId: String) { - // FIXME: 13/10/2020 : New_arch: Copy file - -// if (originalFile != null && originalFile.fileExists() && ROOT_PATH != originalFile.fileName) { -// // 1. Copy in database -// val ocTargetFile = OCFile(targetPath) -// val parentId = getFileByPath(FileStorageUtils.getParentPath(targetPath))!!.fileId -// ocTargetFile.parentId = parentId -// ocTargetFile.remoteId = targetFileRemoteId -// ocTargetFile.fileLength = originalFile.fileLength -// ocTargetFile.mimetype = originalFile.mimetype -// ocTargetFile.modificationTimestamp = System.currentTimeMillis() -// saveFile(ocTargetFile) -// -// // 2. Copy in local file system -// var copied = false -// val localPath = FileStorageUtils.getDefaultSavePathFor(account.name, originalFile) -// val localFile = File(localPath) -// val defaultSavePath = FileStorageUtils.getSavePath(account.name) -// if (localFile.exists()) { -// val targetFile = File(defaultSavePath + targetPath) -// val targetFolder = targetFile.parentFile -// if (targetFolder != null && !targetFolder.exists()) { -// targetFolder.mkdirs() -// } -// copied = copyFile(localFile, targetFile) -// } -// -// Timber.d("Local file COPIED : $copied") -// } - } - - private fun copyFile(src: File, target: File): Boolean { - var ret = true - - var input: InputStream? = null - var out: OutputStream? = null - - try { - input = FileInputStream(src) - out = FileOutputStream(target) - val buf = ByteArray(1024) - var len: Int - do { - len = input.read() - out.write(buf, 0, len) - } while (len > 0) - } catch (ex: IOException) { - ret = false - } finally { - if (input != null) { - try { - input.close() - } catch (e: IOException) { - Timber.e(e) - } - - } - if (out != null) { - try { - out.close() - } catch (e: IOException) { - Timber.e(e) - } - - } - } - - return ret - } - // TODO: New_arch: Remove this and call usecase inside FilesViewModel fun getFolderContent(parentId: Long): List = runBlocking(CoroutinesDispatcherProvider().io) { val getFolderContentUseCase: GetFolderContentUseCase by inject() diff --git a/owncloudApp/src/main/java/com/owncloud/android/operations/CopyFileOperation.java b/owncloudApp/src/main/java/com/owncloud/android/operations/CopyFileOperation.java deleted file mode 100644 index c4d56d11b44..00000000000 --- a/owncloudApp/src/main/java/com/owncloud/android/operations/CopyFileOperation.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * ownCloud Android client application - * - * @author David A. Velasco - * @author David González Verdugo - * Copyright (C) 2020 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 . - */ - - -package com.owncloud.android.operations; - -import com.owncloud.android.domain.files.model.OCFile; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.lib.resources.files.CopyRemoteFileOperation; -import com.owncloud.android.operations.common.SyncOperation; -import com.owncloud.android.utils.RemoteFileUtils; - -import java.io.File; - -/** - * Operation copying an {@link OCFile} to a different folder. - * - * @author David A. Velasco - */ -public class CopyFileOperation extends SyncOperation { - - private String mSrcPath; - private String mTargetParentPath; - - private OCFile mFile; - - /** - * Constructor - * - * @param srcPath Remote path of the {@link OCFile} to move. - * @param targetParentPath Path to the folder where the file will be copied into. - */ - public CopyFileOperation(String srcPath, String targetParentPath) { - mSrcPath = srcPath; - mTargetParentPath = targetParentPath; - if (!mTargetParentPath.endsWith(File.separator)) { - mTargetParentPath += File.separator; - } - - mFile = null; - } - - /** - * Performs the operation. - * - * @param client Client object to communicate with the remote ownCloud server. - */ - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result; - - /// 1. check copy validity - if (mTargetParentPath.startsWith(mSrcPath)) { - return new RemoteOperationResult(ResultCode.INVALID_COPY_INTO_DESCENDANT); - } - mFile = getStorageManager().getFileByPath(mSrcPath); - if (mFile == null) { - return new RemoteOperationResult(ResultCode.FILE_NOT_FOUND); - } - - /// 2. remote copy - String targetRemotePath = mTargetParentPath + mFile.getFileName(); - // Check if target remote path already exists on server or add suffix (2), (3) ... otherwise - String finalRemotePath = RemoteFileUtils.Companion.getAvailableRemotePath(client, targetRemotePath); - - if (mFile.isFolder()) { - finalRemotePath += File.separator; - } - CopyRemoteFileOperation operation = new CopyRemoteFileOperation( - mSrcPath, - finalRemotePath - ); - result = operation.execute(client); - String targetFileRemoteId = (String) result.getData(); - - /// 3. local copy - if (result.isSuccess()) { - getStorageManager().copyLocalFile(mFile, finalRemotePath, targetFileRemoteId); - } - // TODO handle ResultCode.PARTIAL_COPY_DONE in client Activity, for the moment - - return result; - } -} diff --git a/owncloudApp/src/main/java/com/owncloud/android/services/OperationsService.java b/owncloudApp/src/main/java/com/owncloud/android/services/OperationsService.java index b84888b152a..7ef2746afac 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/services/OperationsService.java +++ b/owncloudApp/src/main/java/com/owncloud/android/services/OperationsService.java @@ -48,7 +48,6 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.resources.status.OwnCloudVersion; import com.owncloud.android.operations.CheckCurrentCredentialsOperation; -import com.owncloud.android.operations.CopyFileOperation; import com.owncloud.android.operations.SynchronizeFileOperation; import com.owncloud.android.operations.SynchronizeFolderOperation; import com.owncloud.android.operations.common.SyncOperation; @@ -64,7 +63,6 @@ public class OperationsService extends Service { public static final String EXTRA_ACCOUNT = "ACCOUNT"; public static final String EXTRA_SERVER_URL = "SERVER_URL"; public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH"; - public static final String EXTRA_NEW_PARENT_PATH = "NEW_PARENT_PATH"; public static final String EXTRA_FILE = "FILE"; public static final String EXTRA_PUSH_ONLY = "PUSH_ONLY"; public static final String EXTRA_SYNC_REGULAR_FILES = "SYNC_REGULAR_FILES"; @@ -73,7 +71,6 @@ public class OperationsService extends Service { public static final String ACTION_SYNC_FILE = "SYNC_FILE"; public static final String ACTION_SYNC_FOLDER = "SYNC_FOLDER"; - public static final String ACTION_COPY_FILE = "COPY_FILE"; public static final String ACTION_CHECK_CURRENT_CREDENTIALS = "CHECK_CURRENT_CREDENTIALS"; private ConcurrentMap> @@ -468,14 +465,6 @@ private Pair newOperation(Intent operationIntent) { break; } - case ACTION_COPY_FILE: { - // Copy file/folder - String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH); - String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH); - operation = new CopyFileOperation(remotePath, newParentPath); - - break; - } case ACTION_CHECK_CURRENT_CREDENTIALS: // Check validity of currently stored credentials for a given account operation = new CheckCurrentCredentialsOperation(account); diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/ErrorMessageAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/ErrorMessageAdapter.kt index 3fcc417e1cf..ae80deabd06 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/ErrorMessageAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/ErrorMessageAdapter.kt @@ -29,7 +29,6 @@ import com.owncloud.android.extensions.parseError import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode -import com.owncloud.android.operations.CopyFileOperation import com.owncloud.android.operations.CreateFolderOperation import com.owncloud.android.operations.SynchronizeFileOperation import com.owncloud.android.operations.SynchronizeFolderOperation @@ -144,7 +143,7 @@ class ErrorMessageAdapter { R.string.uploader_upload_forbidden_permissions ) if (operation is CreateFolderOperation) formatter.forbidden(R.string.forbidden_permissions_create) - if (operation is CopyFileOperation) formatter.forbidden(R.string.forbidden_permissions_copy) else formatter.format( + else formatter.format( R.string.filename_forbidden_charaters_from_server ) } @@ -160,16 +159,14 @@ class ErrorMessageAdapter { R.string.sync_current_folder_was_removed, File(operation.folderPath).name ) - if (operation is CopyFileOperation) - formatter.format(R.string.copy_file_not_found) else formatter.format(R.string.rename_local_fail_msg) + else formatter.format(R.string.rename_local_fail_msg) } ResultCode.INVALID_LOCAL_FILE_NAME -> formatter.format(R.string.rename_local_fail_msg) ResultCode.INVALID_CHARACTER_IN_NAME -> formatter.format(R.string.filename_forbidden_characters) ResultCode.INVALID_OVERWRITE -> { - if (operation is CopyFileOperation) formatter.format(R.string.copy_file_invalid_overwrite) - else formatter.format(R.string.move_file_error) + formatter.format(R.string.move_file_error) } ResultCode.CONFLICT -> formatter.format(R.string.move_file_error) ResultCode.INVALID_COPY_INTO_DESCENDANT -> @@ -241,7 +238,6 @@ class ErrorMessageAdapter { R.string.sync_folder_failed_content, File(operation.folderPath).name ) - is CopyFileOperation -> formatter.format(R.string.copy_file_error) // if everything else fails else -> if (result.isSuccess) formatter.format(android.R.string.ok) else formatter.format(R.string.common_error_unknown) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 9378b3a4040..abc41c68693 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -318,24 +318,6 @@ public void cancelTransference(OCFile file) { } } - /** - * Start operations to copy one or several files - * - * @param files Files to copy - * @param targetFolder Folder where the files while be copied into - */ - public void copyFiles(Collection files, OCFile targetFolder) { - for (OCFile file : files) { - Intent service = new Intent(mFileActivity, OperationsService.class); - service.setAction(OperationsService.ACTION_COPY_FILE); - service.putExtra(OperationsService.EXTRA_NEW_PARENT_PATH, targetFolder.getRemotePath()); - service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); - service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount()); - mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service); - } - mFileActivity.showLoadingDialog(R.string.wait_a_moment); - } - public long getOpIdWaitingFor() { return mWaitingForOpId; } From cd00decc87d313b14030b0b35832c7ebaacd7753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 16:24:41 +0200 Subject: [PATCH 12/13] Remove unused imports --- .../com/owncloud/android/datamodel/FileDataStorageManager.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt b/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt index 38aa775a879..7fe660ab622 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt @@ -93,11 +93,6 @@ import org.koin.core.KoinComponent import org.koin.core.inject import timber.log.Timber import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream import java.util.Vector class FileDataStorageManager : KoinComponent { From ea31b4fdbd374a922e6a6b18fcfab5b15d396d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Garci=CC=81a=20de=20Prada?= Date: Tue, 25 May 2021 17:01:22 +0200 Subject: [PATCH 13/13] Add unit tests for copy file use case --- .../files/usecases/CopyFileUseCaseTest.kt | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 owncloudDomain/src/test/java/com/owncloud/android/domain/files/usecases/CopyFileUseCaseTest.kt diff --git a/owncloudDomain/src/test/java/com/owncloud/android/domain/files/usecases/CopyFileUseCaseTest.kt b/owncloudDomain/src/test/java/com/owncloud/android/domain/files/usecases/CopyFileUseCaseTest.kt new file mode 100644 index 00000000000..f6984b64793 --- /dev/null +++ b/owncloudDomain/src/test/java/com/owncloud/android/domain/files/usecases/CopyFileUseCaseTest.kt @@ -0,0 +1,99 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2021 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 . + */ +package com.owncloud.android.domain.files.usecases + +import com.owncloud.android.domain.exceptions.CopyIntoDescendantException +import com.owncloud.android.domain.exceptions.UnauthorizedException +import com.owncloud.android.domain.files.FileRepository +import com.owncloud.android.testutil.OC_FILE +import com.owncloud.android.testutil.OC_FOLDER +import io.mockk.every +import io.mockk.spyk +import io.mockk.verify +import org.junit.Assert.assertTrue +import org.junit.Test + +class CopyFileUseCaseTest { + private val repository: FileRepository = spyk() + private val useCase = CopyFileUseCase(repository) + private val useCaseParams = CopyFileUseCase.Params( + listOfFilesToCopy = listOf(OC_FILE.copy(remotePath = "/video.mp4")), + targetFolder = OC_FOLDER + ) + + @Test + fun `copy file - ok`() { + every { repository.copyFile(any(), any()) } returns Unit + + val useCaseResult = useCase.execute(useCaseParams) + + assertTrue(useCaseResult.isSuccess) + + verify(exactly = 1) { repository.copyFile(any(), any()) } + } + + @Test + fun `copy file - ko - empty list`() { + val useCaseResult = useCase.execute(useCaseParams.copy(listOfFilesToCopy = listOf(), targetFolder = OC_FOLDER)) + + assertTrue(useCaseResult.isError) + assertTrue(useCaseResult.getThrowableOrNull() is IllegalArgumentException) + + verify(exactly = 0) { repository.copyFile(any(), any()) } + } + + @Test + fun `copy file - ko - single copy into descendant`() { + val useCaseParams = CopyFileUseCase.Params( + listOf(OC_FOLDER.copy(remotePath = "/Directory")), + OC_FOLDER.copy(remotePath = "/Directory/Descendant/") + ) + val useCaseResult = useCase.execute(useCaseParams) + + assertTrue(useCaseResult.isError) + assertTrue(useCaseResult.getThrowableOrNull() is CopyIntoDescendantException) + + verify(exactly = 0) { repository.copyFile(any(), any()) } + } + + @Test + fun `copy file - ko - multiple copy into descendant`() { + val useCaseParams = CopyFileUseCase.Params( + listOf(OC_FOLDER.copy(remotePath = "/Directory"), OC_FILE.copy(remotePath = "/Document.pdf")), + OC_FOLDER.copy(remotePath = "/Directory/Descendant/") + ) + val useCaseResult = useCase.execute(useCaseParams) + + assertTrue(useCaseResult.isSuccess) + + verify(exactly = 1) { repository.copyFile(any(), any()) } + } + + @Test + fun `copy file - ko - other exception`() { + every { repository.copyFile(any(), any()) } throws UnauthorizedException() + + val useCaseResult = useCase.execute(useCaseParams) + + assertTrue(useCaseResult.isError) + assertTrue(useCaseResult.getThrowableOrNull() is UnauthorizedException) + + verify(exactly = 1) { repository.copyFile(any(), any()) } + } +}