diff --git a/app/src/androidTest/java/com/owncloud/android/util/ErrorMessageAdapterIT.java b/app/src/androidTest/java/com/owncloud/android/util/ErrorMessageAdapterIT.java deleted file mode 100644 index 1e6b4ee01f11..000000000000 --- a/app/src/androidTest/java/com/owncloud/android/util/ErrorMessageAdapterIT.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2021-2022 Chris Narkiewicz - * SPDX-FileCopyrightText: 2019-2021 Tobias Kaminsky - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package com.owncloud.android.util; - -import android.content.Context; -import android.content.res.Resources; - -import com.nextcloud.client.account.MockUser; -import com.nextcloud.client.account.User; -import com.owncloud.android.MainApp; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.operations.RemoveFileOperation; -import com.owncloud.android.utils.ErrorMessageAdapter; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import static junit.framework.TestCase.assertEquals; - -@RunWith(AndroidJUnit4.class) -public class ErrorMessageAdapterIT { - private final static String PATH_TO_DELETE = "/path/to/a.file"; - private final static String EXPECTED_ERROR_MESSAGE = "You are not permitted to delete this file"; - private final static String ACCOUNT_TYPE = "nextcloud"; - - @Test - public void getErrorCauseMessageForForbiddenRemoval() { - Resources resources = InstrumentationRegistry.getInstrumentation().getTargetContext().getResources(); - User user = new MockUser("name", ACCOUNT_TYPE); - Context context = MainApp.getAppContext(); - - String errorMessage = ErrorMessageAdapter.getErrorCauseMessage( - new RemoteOperationResult(RemoteOperationResult.ResultCode.FORBIDDEN), - new RemoveFileOperation(new OCFile(PATH_TO_DELETE), - false, - user, - false, - context, - new FileDataStorageManager(user, context.getContentResolver())), - resources - ); - - assertEquals(EXPECTED_ERROR_MESSAGE, errorMessage); - } -} diff --git a/app/src/androidTest/java/com/owncloud/android/util/ErrorMessageAdapterIT.kt b/app/src/androidTest/java/com/owncloud/android/util/ErrorMessageAdapterIT.kt new file mode 100644 index 000000000000..ae5d88c50ecb --- /dev/null +++ b/app/src/androidTest/java/com/owncloud/android/util/ErrorMessageAdapterIT.kt @@ -0,0 +1,54 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-FileCopyrightText: 2021-2022 Chris Narkiewicz + * SPDX-FileCopyrightText: 2019-2021 Tobias Kaminsky + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ +package com.owncloud.android.util + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.nextcloud.client.account.MockUser +import com.nextcloud.client.account.User +import com.owncloud.android.MainApp +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.operations.RemoveFileOperation +import com.owncloud.android.utils.ErrorMessageAdapter.getErrorCauseMessage +import junit.framework.TestCase +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ErrorMessageAdapterIT { + @Test + fun getErrorCauseMessageForForbiddenRemoval() { + val resources = InstrumentationRegistry.getInstrumentation().targetContext.resources + val user: User = MockUser("name", ACCOUNT_TYPE) + val context = MainApp.getAppContext() + + val errorMessage = getErrorCauseMessage( + RemoteOperationResult(RemoteOperationResult.ResultCode.FORBIDDEN), + RemoveFileOperation( + OCFile(PATH_TO_DELETE), + false, + user, + false, + context, + FileDataStorageManager(user, context.contentResolver) + ), + resources + ) + + TestCase.assertEquals(EXPECTED_ERROR_MESSAGE, errorMessage) + } + + companion object { + private const val PATH_TO_DELETE = "/path/to/a.file" + private const val EXPECTED_ERROR_MESSAGE = "You are not permitted to delete this file" + private const val ACCOUNT_TYPE = "nextcloud" + } +} diff --git a/app/src/main/java/com/owncloud/android/utils/ErrorMessageAdapter.java b/app/src/main/java/com/owncloud/android/utils/ErrorMessageAdapter.java deleted file mode 100644 index 6f40e432901d..000000000000 --- a/app/src/main/java/com/owncloud/android/utils/ErrorMessageAdapter.java +++ /dev/null @@ -1,544 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2017-2021 Andy Scherzinger - * SPDX-FileCopyrightText: 2017 Tobias Kaminsky - * SPDX-FileCopyrightText: 2016 ownCloud Inc. - * SPDX-FileCopyrightText: 2015 María Asensio Valverde - * SPDX-FileCopyrightText: 2026 TSI-mc - * SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only) - */ -package com.owncloud.android.utils; - -import android.content.res.Resources; -import android.text.TextUtils; - -import com.owncloud.android.R; -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.CreateShareViaLinkOperation; -import com.owncloud.android.operations.CreateShareWithShareeOperation; -import com.owncloud.android.operations.DownloadFileOperation; -import com.owncloud.android.operations.MoveFileOperation; -import com.owncloud.android.operations.RemoveFileOperation; -import com.owncloud.android.operations.RenameFileOperation; -import com.owncloud.android.operations.SynchronizeFileOperation; -import com.owncloud.android.operations.SynchronizeFolderOperation; -import com.owncloud.android.operations.UnshareOperation; -import com.owncloud.android.operations.UpdateSharePermissionsOperation; -import com.owncloud.android.operations.UpdateShareViaLinkOperation; -import com.owncloud.android.operations.UploadFileOperation; -import com.owncloud.android.operations.albums.CopyFileToAlbumOperation; -import com.owncloud.android.lib.resources.albums.RenameAlbumRemoteOperation; - -import org.apache.commons.httpclient.ConnectTimeoutException; -import org.apache.commons.httpclient.HttpStatus; - -import java.io.File; -import java.net.SocketTimeoutException; -import java.util.Optional; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * Class to choose proper error messages to show to the user depending on the results of operations, - * always following the same policy - */ -public final class ErrorMessageAdapter { - - private ErrorMessageAdapter() { - // utility class -> private constructor - } - - /** - * Return an internationalized user message corresponding to an operation result - * and the operation performed. - * - * @param result Result of a {@link RemoteOperation} performed. - * @param operation Operation performed. - * @param res Reference to app resources, for i18n. - * @return User message corresponding to 'result' and 'operation'. - */ - public static @NonNull - String getErrorCauseMessage( - RemoteOperationResult result, - RemoteOperation operation, - Resources res) { - - String message = getMessageForResultAndOperation(result, operation, res); - - if (TextUtils.isEmpty(message)) { - message = getMessageForResult(result, res); - } - - if (TextUtils.isEmpty(message)) { - message = getMessageForOperation(operation, res); - } - - if (message == null) { - if (result.isSuccess()) { - message = res.getString(R.string.common_ok); - - } else { - message = res.getString(R.string.common_error_unknown); - } - } - - return message; - } - - /** - * Return a user message corresponding to an operation result and specific for the operation - * performed. - * - * @param result Result of a {@link RemoteOperation} performed. - * @param operation Operation performed. - * @param res Reference to app resources, for i18n. - * @return User message corresponding to 'result' and 'operation', or NULL if there is no - * specific message for both. - */ - private static @Nullable - String getMessageForResultAndOperation( - RemoteOperationResult result, - RemoteOperation operation, - Resources res) { - - String message = null; - - if (operation instanceof UploadFileOperation) { - message = getMessageForUploadFileOperation(result, (UploadFileOperation) operation, res); - - } else if (operation instanceof DownloadFileOperation) { - message = getMessageForDownloadFileOperation(result, (DownloadFileOperation) operation, res); - - } else if (operation instanceof RemoveFileOperation) { - message = getMessageForRemoveFileOperation(result, res); - - } else if (operation instanceof RenameFileOperation) { - message = getMessageForRenameFileOperation(result, res); - - } else if (operation instanceof SynchronizeFileOperation) { - if (!((SynchronizeFileOperation) operation).getTransferWasRequested()) { - message = res.getString(R.string.sync_file_nothing_to_do_msg); - } - - } else if (operation instanceof CreateFolderOperation) { - message = getMessageForCreateFolderOperation(result, res); - - } else if (operation instanceof CreateShareViaLinkOperation || - operation instanceof CreateShareWithShareeOperation) { - - message = getMessageForCreateShareOperations(result, operation, res); - - } else if (operation instanceof UnshareOperation) { - - message = getMessageForUnshareOperation(result, res); - - } else if (operation instanceof UpdateShareViaLinkOperation || - operation instanceof UpdateSharePermissionsOperation) { - - message = getMessageForUpdateShareOperations(result, res); - - } else if (operation instanceof MoveFileOperation) { - - message = getMessageForMoveFileOperation(result, res); - - } else if (operation instanceof SynchronizeFolderOperation) { - - message = getMessageForSynchronizeFolderOperation(result, (SynchronizeFolderOperation) operation, res); - - } else if (operation instanceof CopyFileOperation) { - message = getMessageForCopyFileOperation(result, res); - } else if (operation instanceof CopyFileToAlbumOperation) { - message = getMessageForCopyFileToAlbumOperation(result, res); - } else if (operation instanceof RenameAlbumRemoteOperation) { - message = getMessageForRenameAlbumOperation(result, res); - } - - return message; - } - - private static @Nullable - String getMessageForSynchronizeFolderOperation( - RemoteOperationResult result, - SynchronizeFolderOperation operation, - Resources res) { - - if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND) { - Optional folderName = operation.getFolderNameFromPath(); - if (folderName.isEmpty()) { - return null; - } - - String format = res.getString(R.string.sync_current_folder_was_removed); - return String.format(format, folderName.get()); - } - return null; - } - - private static @Nullable - String getMessageForMoveFileOperation(RemoteOperationResult result, Resources res) { - if (result.getCode() == ResultCode.FILE_NOT_FOUND) { - return res.getString(R.string.move_file_not_found); - } else if (result.getCode() == ResultCode.INVALID_MOVE_INTO_DESCENDANT) { - return res.getString(R.string.move_file_invalid_into_descendent); - - } else if (result.getCode() == ResultCode.INVALID_OVERWRITE) { - return res.getString(R.string.move_file_invalid_overwrite); - - } else if (result.getCode() == ResultCode.FORBIDDEN) { - return String.format(res.getString(R.string.forbidden_permissions), - res.getString(R.string.forbidden_permissions_move)); - - } else if (result.getCode() == ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER) { - return res.getString(R.string.filename_forbidden_charaters_from_server); - } - return null; - } - - private static @Nullable - String getMessageForUpdateShareOperations(RemoteOperationResult result, Resources res) { - if (!TextUtils.isEmpty(result.getMessage())) { - return result.getMessage(); // share API sends its own error messages - } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) { - return res.getString(R.string.update_link_file_no_exist); - } else if (result.getCode() == ResultCode.SHARE_FORBIDDEN) { - // Error --> No permissions - return String.format(res.getString(R.string.forbidden_permissions), - res.getString(R.string.update_link_forbidden_permissions)); - } - return null; - } - - private static @Nullable - String getMessageForUnshareOperation(RemoteOperationResult result, Resources res) { - if (!TextUtils.isEmpty(result.getMessage())) { - return result.getMessage(); // share API sends its own error messages - } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) { - return res.getString(R.string.unshare_link_file_no_exist); - } else if (result.getCode() == ResultCode.SHARE_FORBIDDEN) { - // Error --> No permissions - return String.format(res.getString(R.string.forbidden_permissions), - res.getString(R.string.unshare_link_forbidden_permissions)); - } - return null; - } - - private static @Nullable - String getMessageForCopyFileOperation(RemoteOperationResult result, Resources res) { - if (result.getCode() == ResultCode.FILE_NOT_FOUND) { - return res.getString(R.string.copy_file_not_found); - - } else if (result.getCode() == ResultCode.INVALID_COPY_INTO_DESCENDANT) { - return res.getString(R.string.copy_file_invalid_into_descendent); - - } else if (result.getCode() == ResultCode.INVALID_OVERWRITE) { - return res.getString(R.string.copy_file_invalid_overwrite); - - } else if (result.getCode() == ResultCode.FORBIDDEN) { - return String.format(res.getString(R.string.forbidden_permissions), - res.getString(R.string.forbidden_permissions_copy)); - - } - return null; - } - - private static @Nullable - String getMessageForCreateShareOperations(RemoteOperationResult result, RemoteOperation operation, Resources res) { - if (!TextUtils.isEmpty(result.getMessage())) { - return result.getMessage(); // share API sends its own error messages - } else if (isLinkSharePasswordRejected(result, operation)) { - return res.getString(R.string.share_link_password_rejected); - } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) { - return res.getString(R.string.share_link_file_no_exist); - } else if (result.getCode() == ResultCode.SHARE_FORBIDDEN) { - // Error --> No permissions - return String.format(res.getString(R.string.forbidden_permissions), - res.getString(R.string.share_link_forbidden_permissions)); - } - return null; - } - - private static boolean isLinkSharePasswordRejected(RemoteOperationResult result, RemoteOperation operation) { - return operation instanceof CreateShareViaLinkOperation createShareViaLinkOperation && - !TextUtils.isEmpty(createShareViaLinkOperation.getPassword()) && - result.getHttpCode() == HttpStatus.SC_BAD_REQUEST; - } - - private static @Nullable - String getMessageForCreateFolderOperation(RemoteOperationResult result, Resources res) { - if (result.getCode() == ResultCode.INVALID_CHARACTER_IN_NAME) { - return res.getString(R.string.filename_forbidden_characters); - - } else if (result.getCode() == ResultCode.FORBIDDEN) { - return String.format(res.getString(R.string.forbidden_permissions), - res.getString(R.string.forbidden_permissions_create)); - - } else if (result.getCode() == ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER) { - return res.getString(R.string.filename_forbidden_charaters_from_server); - - } - return null; - } - - private static @Nullable - String getMessageForRenameFileOperation(RemoteOperationResult result, Resources res) { - if (result.getCode() == ResultCode.INVALID_LOCAL_FILE_NAME) { - return res.getString(R.string.rename_local_fail_msg); - - } else if (result.getCode() == ResultCode.FORBIDDEN) { - // Error --> No permissions - return String.format(res.getString(R.string.forbidden_permissions), - res.getString(R.string.forbidden_permissions_rename)); - - } else if (result.getCode() == ResultCode.INVALID_CHARACTER_IN_NAME) { - return res.getString(R.string.filename_forbidden_characters); - - } else if (result.getCode() == ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER) { - return res.getString(R.string.filename_forbidden_charaters_from_server); - - } - - return null; - } - - private static @Nullable - String getMessageForRemoveFileOperation(RemoteOperationResult result, Resources res) { - if (result.isSuccess()) { - return res.getString(R.string.remove_success_msg); - - } else { - if (result.getCode() == ResultCode.FORBIDDEN) { - return String.format(res.getString(R.string.forbidden_permissions), res.getString(R.string.forbidden_permissions_delete)); - } else if (result.getCode() == ResultCode.LOCKED) { - return res.getString(R.string.preview_media_unhandled_http_code_message); - } - } - - return null; - } - - private static @Nullable - String getMessageForDownloadFileOperation( - RemoteOperationResult result, - DownloadFileOperation operation, - Resources res) { - - if (result.isSuccess()) { - return String.format( - res.getString(R.string.downloader_download_succeeded_content), - new File(operation.getSavePath()).getName()); - - } else { - switch (result.getCode()) { - case FILE_NOT_FOUND: - return res.getString(R.string.downloader_download_file_not_found); - case CANNOT_CREATE_FILE: - return res.getString(R.string.download_cannot_create_file); - case INVALID_LOCAL_FILE_NAME: - return res.getString(R.string.download_download_invalid_local_file_name); - } - } - return null; - } - - private static @Nullable - String getMessageForUploadFileOperation( - RemoteOperationResult result, - UploadFileOperation operation, - Resources res) { - - if (result.isSuccess()) { - return String.format( - res.getString(R.string.uploader_upload_succeeded_content_single), - operation.getFileName()); - } else { - - if (result.getCode() == ResultCode.LOCAL_STORAGE_FULL - || result.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) { - return String.format( - res.getString(R.string.error__upload__local_file_not_copied), - operation.getFileName(), - res.getString(R.string.app_name)); - - } else if (result.getCode() == ResultCode.FORBIDDEN) { - return String.format(res.getString(R.string.forbidden_permissions), - res.getString(R.string.uploader_upload_forbidden_permissions)); - - } else if (result.getCode() == ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER) { - return res.getString(R.string.filename_forbidden_charaters_from_server); - - } else if(result.getCode() == ResultCode.SYNC_CONFLICT) { - return String.format(res.getString(R.string.uploader_upload_failed_sync_conflict_error_content), - operation.getFileName()); - } - } - - return null; - } - - - /** - * Return a user message corresponding to an operation result with no knowledge about the operation - * performed. - * - * @param result Result of a {@link RemoteOperation} performed. - * @param res Reference to app resources, for i18n. - * @return User message corresponding to 'result'. - */ - private static @Nullable - String getMessageForResult(RemoteOperationResult result, Resources res) { - String message = null; - - if (!result.isSuccess()) { - if (result.getCode() == ResultCode.WRONG_CONNECTION) { - message = res.getString(R.string.network_error_socket_exception); - - } else if (result.getCode() == ResultCode.TIMEOUT) { - message = res.getString(R.string.network_error_socket_exception); - - if (result.getException() instanceof SocketTimeoutException) { - message = res.getString(R.string.network_error_socket_timeout_exception); - - } else if (result.getException() instanceof ConnectTimeoutException) { - message = res.getString(R.string.network_error_connect_timeout_exception); - } - - } else if (result.getCode() == ResultCode.HOST_NOT_AVAILABLE) { - message = res.getString(R.string.network_host_not_available); - - } else if (result.getCode() == ResultCode.MAINTENANCE_MODE) { - message = res.getString(R.string.maintenance_mode); - - } else if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { - message = res.getString(R.string.uploads_view_upload_status_failed_ssl_certificate_not_trusted); - - } else if (result.getCode() == ResultCode.BAD_OC_VERSION) { - message = res.getString(R.string.auth_bad_oc_version_title); - - } else if (result.getCode() == ResultCode.INCORRECT_ADDRESS) { - message = res.getString(R.string.auth_incorrect_address_title); - - } else if (result.getCode() == ResultCode.SSL_ERROR) { - message = res.getString(R.string.auth_ssl_general_error_title); - - } else if (result.getCode() == ResultCode.UNAUTHORIZED) { - message = res.getString(R.string.auth_unauthorized); - - } else if (result.getCode() == ResultCode.INSTANCE_NOT_CONFIGURED) { - message = res.getString(R.string.auth_not_configured_title); - - } else if (result.getCode() == ResultCode.FILE_NOT_FOUND) { - message = res.getString(R.string.file_not_found); - - } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { - message = res.getString(R.string.auth_oauth_error); - - } else if (result.getCode() == ResultCode.OAUTH2_ERROR_ACCESS_DENIED) { - message = res.getString(R.string.auth_oauth_error_access_denied); - - } else if (result.getCode() == ResultCode.ACCOUNT_NOT_NEW) { - message = res.getString(R.string.auth_account_not_new); - - } else if (result.getCode() == ResultCode.ACCOUNT_NOT_THE_SAME) { - message = res.getString(R.string.auth_account_not_the_same); - - } else if (result.getCode() == ResultCode.QUOTA_EXCEEDED) { - message = res.getString(R.string.upload_quota_exceeded); - - } else if (result.getCode() == ResultCode.LOCKED) { - message = res.getString(R.string.upload_locked_message); - } - - else if (!TextUtils.isEmpty(result.getHttpPhrase())) { - // last chance: error message from server - message = result.getHttpPhrase(); - } - } - - return message; - } - - /** - * Return a user message corresponding to a generic error for a given operation. - * - * @param operation Operation performed. - * @param res Reference to app resources, for i18n. - * @return User message corresponding to a generic error of 'operation'. - */ - private static @Nullable - String getMessageForOperation(RemoteOperation operation, Resources res) { - String message = null; - - if (operation instanceof UploadFileOperation) { - message = String.format( - res.getString(R.string.uploader_upload_failed_content_single), - ((UploadFileOperation) operation).getFileName()); - - } else if (operation instanceof DownloadFileOperation) { - message = String.format( - res.getString(R.string.downloader_download_failed_content), - new File(((DownloadFileOperation) operation).getSavePath()).getName() - ); - - } else if (operation instanceof RemoveFileOperation) { - message = res.getString(R.string.remove_fail_msg); - - } else if (operation instanceof RenameFileOperation) { - message = res.getString(R.string.rename_server_fail_msg); - - } else if (operation instanceof CreateFolderOperation) { - message = res.getString(R.string.create_dir_fail_msg); - - } else if (operation instanceof CreateShareViaLinkOperation || - operation instanceof CreateShareWithShareeOperation - ) { - message = res.getString(R.string.share_link_file_error); - - } else if (operation instanceof UnshareOperation) { - message = res.getString(R.string.unshare_link_file_error); - - } else if (operation instanceof UpdateShareViaLinkOperation || - operation instanceof UpdateSharePermissionsOperation - ) { - message = res.getString(R.string.update_link_file_error); - - } else if (operation instanceof MoveFileOperation) { - message = res.getString(R.string.move_file_error); - - } else if (operation instanceof SynchronizeFolderOperation synchronizeFolderOperation) { - Optional folderName = synchronizeFolderOperation.getFolderNameFromPath(); - if (folderName.isEmpty()) { - return null; - } - - String format = res.getString(R.string.sync_folder_failed_content); - message = String.format(format, folderName.get()); - - } else if (operation instanceof CopyFileOperation) { - message = res.getString(R.string.copy_file_error); - } - - return message; - } - - private static @Nullable - String getMessageForCopyFileToAlbumOperation(RemoteOperationResult result, Resources res) { - if (result.getCode() == ResultCode.CONFLICT) { - return res.getString(R.string.album_copy_file_conflict); - } - return null; - } - - private static @Nullable - String getMessageForRenameAlbumOperation(RemoteOperationResult result, Resources res) { - if (result.getCode() == ResultCode.INVALID_OVERWRITE) { - return res.getString(R.string.album_rename_conflict); - } - return null; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/owncloud/android/utils/ErrorMessageAdapter.kt b/app/src/main/java/com/owncloud/android/utils/ErrorMessageAdapter.kt new file mode 100644 index 000000000000..a51c818d5384 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/utils/ErrorMessageAdapter.kt @@ -0,0 +1,284 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-FileCopyrightText: 2017-2021 Andy Scherzinger + * SPDX-FileCopyrightText: 2017 Tobias Kaminsky + * SPDX-FileCopyrightText: 2016 ownCloud Inc. + * SPDX-FileCopyrightText: 2015 María Asensio Valverde + * SPDX-FileCopyrightText: 2026 TSI-mc + * SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only) + */ +package com.owncloud.android.utils + +import android.content.res.Resources +import androidx.annotation.StringRes +import com.owncloud.android.R +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.albums.RenameAlbumRemoteOperation +import com.owncloud.android.operations.CopyFileOperation +import com.owncloud.android.operations.CreateFolderOperation +import com.owncloud.android.operations.CreateShareViaLinkOperation +import com.owncloud.android.operations.CreateShareWithShareeOperation +import com.owncloud.android.operations.DownloadFileOperation +import com.owncloud.android.operations.MoveFileOperation +import com.owncloud.android.operations.RemoveFileOperation +import com.owncloud.android.operations.RenameFileOperation +import com.owncloud.android.operations.SynchronizeFileOperation +import com.owncloud.android.operations.SynchronizeFolderOperation +import com.owncloud.android.operations.UnshareOperation +import com.owncloud.android.operations.UpdateSharePermissionsOperation +import com.owncloud.android.operations.UpdateShareViaLinkOperation +import com.owncloud.android.operations.UploadFileOperation +import com.owncloud.android.operations.albums.CopyFileToAlbumOperation +import org.apache.commons.httpclient.ConnectTimeoutException +import org.apache.commons.httpclient.HttpStatus +import java.io.File +import java.net.SocketTimeoutException +import kotlin.jvm.optionals.getOrNull + +object ErrorMessageAdapter { + + private val failedResultMessages: Map = mapOf( + ResultCode.WRONG_CONNECTION to R.string.network_error_socket_exception, + ResultCode.HOST_NOT_AVAILABLE to R.string.network_host_not_available, + ResultCode.MAINTENANCE_MODE to R.string.maintenance_mode, + ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED to + R.string.uploads_view_upload_status_failed_ssl_certificate_not_trusted, + ResultCode.BAD_OC_VERSION to R.string.auth_bad_oc_version_title, + ResultCode.INCORRECT_ADDRESS to R.string.auth_incorrect_address_title, + ResultCode.SSL_ERROR to R.string.auth_ssl_general_error_title, + ResultCode.UNAUTHORIZED to R.string.auth_unauthorized, + ResultCode.INSTANCE_NOT_CONFIGURED to R.string.auth_not_configured_title, + ResultCode.FILE_NOT_FOUND to R.string.file_not_found, + ResultCode.OAUTH2_ERROR to R.string.auth_oauth_error, + ResultCode.OAUTH2_ERROR_ACCESS_DENIED to R.string.auth_oauth_error_access_denied, + ResultCode.ACCOUNT_NOT_NEW to R.string.auth_account_not_new, + ResultCode.ACCOUNT_NOT_THE_SAME to R.string.auth_account_not_the_same, + ResultCode.QUOTA_EXCEEDED to R.string.upload_quota_exceeded, + ResultCode.LOCKED to R.string.upload_locked_message + ) + + @JvmStatic + fun getErrorCauseMessage(result: RemoteOperationResult<*>, operation: RemoteOperation<*>?, res: Resources): String = + getMessageForResultAndOperation(result, operation, res)?.takeIf { it.isNotEmpty() } + ?: getMessageForResult(result, res)?.takeIf { it.isNotEmpty() } + ?: getMessageForOperation(operation, res) + ?: res.getString(if (result.isSuccess) R.string.common_ok else R.string.common_error_unknown) + + @Suppress("CyclomaticComplexMethod") + private fun getMessageForResultAndOperation( + result: RemoteOperationResult<*>, + operation: RemoteOperation<*>?, + res: Resources + ): String? = when (operation) { + is UploadFileOperation -> getUploadMessage(result, operation, res) + is DownloadFileOperation -> getDownloadMessage(result, operation, res) + is RemoveFileOperation -> getRemoveMessage(result, res) + is RenameFileOperation -> getRenameMessage(result, res) + is SynchronizeFileOperation -> getSyncFileMessage(operation, res) + is CreateFolderOperation -> getCreateFolderMessage(result, res) + is CreateShareViaLinkOperation -> getCreateShareMessage(result, operation, res) + is CreateShareWithShareeOperation -> getCreateShareMessage(result, operation, res) + is UnshareOperation -> getUnshareMessage(result, res) + is UpdateShareViaLinkOperation, is UpdateSharePermissionsOperation -> getUpdateShareMessage(result, res) + is MoveFileOperation -> getMoveMessage(result, res) + is SynchronizeFolderOperation -> getSyncFolderMessage(result, operation, res) + is CopyFileOperation -> getCopyMessage(result, res) + is CopyFileToAlbumOperation -> getCopyToAlbumMessage(result, res) + is RenameAlbumRemoteOperation -> getRenameAlbumMessage(result, res) + else -> null + } + + private fun getSyncFileMessage(operation: SynchronizeFileOperation, res: Resources): String? = + if (operation.transferWasRequested) null else res.getString(R.string.sync_file_nothing_to_do_msg) + + private fun getSyncFolderMessage( + result: RemoteOperationResult<*>, + operation: SynchronizeFolderOperation, + res: Resources + ): String? { + if (result.isSuccess || result.code != ResultCode.FILE_NOT_FOUND) return null + val folderName = operation.folderNameFromPath.getOrNull() ?: return null + return res.getString(R.string.sync_current_folder_was_removed, folderName) + } + + private fun getMoveMessage(result: RemoteOperationResult<*>, res: Resources): String? = when (result.code) { + ResultCode.FILE_NOT_FOUND -> res.getString(R.string.move_file_not_found) + ResultCode.INVALID_MOVE_INTO_DESCENDANT -> res.getString(R.string.move_file_invalid_into_descendent) + ResultCode.INVALID_OVERWRITE -> res.getString(R.string.move_file_invalid_overwrite) + ResultCode.FORBIDDEN -> res.getForbiddenMessage(R.string.forbidden_permissions_move) + ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER -> res.getServerForbiddenCharactersMessage() + else -> null + } + + private fun getUpdateShareMessage(result: RemoteOperationResult<*>, res: Resources): String? = + result.shareApiMessage ?: when (result.code) { + ResultCode.SHARE_NOT_FOUND -> res.getString(R.string.update_link_file_no_exist) + ResultCode.SHARE_FORBIDDEN -> res.getForbiddenMessage(R.string.update_link_forbidden_permissions) + else -> null + } + + private fun getUnshareMessage(result: RemoteOperationResult<*>, res: Resources): String? = + result.shareApiMessage ?: when (result.code) { + ResultCode.SHARE_NOT_FOUND -> res.getString(R.string.unshare_link_file_no_exist) + ResultCode.SHARE_FORBIDDEN -> res.getForbiddenMessage(R.string.unshare_link_forbidden_permissions) + else -> null + } + + private fun getCopyMessage(result: RemoteOperationResult<*>, res: Resources): String? = when (result.code) { + ResultCode.FILE_NOT_FOUND -> res.getString(R.string.copy_file_not_found) + ResultCode.INVALID_COPY_INTO_DESCENDANT -> res.getString(R.string.copy_file_invalid_into_descendent) + ResultCode.INVALID_OVERWRITE -> res.getString(R.string.copy_file_invalid_overwrite) + ResultCode.FORBIDDEN -> res.getForbiddenMessage(R.string.forbidden_permissions_copy) + else -> null + } + + private fun getCreateShareMessage( + result: RemoteOperationResult<*>, + operation: RemoteOperation<*>, + res: Resources + ): String? = result.shareApiMessage ?: when { + isLinkSharePasswordRejected(result, operation) -> res.getString(R.string.share_link_password_rejected) + result.code == ResultCode.SHARE_NOT_FOUND -> res.getString(R.string.share_link_file_no_exist) + result.code == ResultCode.SHARE_FORBIDDEN -> res.getForbiddenMessage(R.string.share_link_forbidden_permissions) + else -> null + } + + private fun isLinkSharePasswordRejected(result: RemoteOperationResult<*>, operation: RemoteOperation<*>): Boolean = + operation is CreateShareViaLinkOperation && + !operation.password.isNullOrEmpty() && + result.httpCode == HttpStatus.SC_BAD_REQUEST + + private fun getCreateFolderMessage(result: RemoteOperationResult<*>, res: Resources): String? = when (result.code) { + ResultCode.INVALID_CHARACTER_IN_NAME -> res.getString(R.string.filename_forbidden_characters) + ResultCode.FORBIDDEN -> res.getForbiddenMessage(R.string.forbidden_permissions_create) + ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER -> res.getServerForbiddenCharactersMessage() + else -> null + } + + private fun getRenameMessage(result: RemoteOperationResult<*>, res: Resources): String? = when (result.code) { + ResultCode.INVALID_LOCAL_FILE_NAME -> res.getString(R.string.rename_local_fail_msg) + ResultCode.FORBIDDEN -> res.getForbiddenMessage(R.string.forbidden_permissions_rename) + ResultCode.INVALID_CHARACTER_IN_NAME -> res.getString(R.string.filename_forbidden_characters) + ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER -> res.getServerForbiddenCharactersMessage() + else -> null + } + + private fun getRemoveMessage(result: RemoteOperationResult<*>, res: Resources): String? { + if (result.isSuccess) return res.getString(R.string.remove_success_msg) + + return when (result.code) { + ResultCode.FORBIDDEN -> res.getForbiddenMessage(R.string.forbidden_permissions_delete) + ResultCode.LOCKED -> res.getString(R.string.preview_media_unhandled_http_code_message) + else -> null + } + } + + private fun getDownloadMessage( + result: RemoteOperationResult<*>, + operation: DownloadFileOperation, + res: Resources + ): String? { + if (result.isSuccess) { + return res.getString(R.string.downloader_download_succeeded_content, operation.savedFileName) + } + + return when (result.code) { + ResultCode.FILE_NOT_FOUND -> res.getString(R.string.downloader_download_file_not_found) + ResultCode.CANNOT_CREATE_FILE -> res.getString(R.string.download_cannot_create_file) + ResultCode.INVALID_LOCAL_FILE_NAME -> res.getString(R.string.download_download_invalid_local_file_name) + else -> null + } + } + + private fun getUploadMessage( + result: RemoteOperationResult<*>, + operation: UploadFileOperation, + res: Resources + ): String? { + if (result.isSuccess) { + return res.getString(R.string.uploader_upload_succeeded_content_single, operation.fileName) + } + + return when (result.code) { + ResultCode.LOCAL_STORAGE_FULL, + ResultCode.LOCAL_STORAGE_NOT_COPIED -> res.getString( + R.string.error__upload__local_file_not_copied, + operation.fileName, + res.getString(R.string.app_name) + ) + + ResultCode.FORBIDDEN -> res.getForbiddenMessage(R.string.uploader_upload_forbidden_permissions) + + ResultCode.INVALID_CHARACTER_DETECT_IN_SERVER -> res.getServerForbiddenCharactersMessage() + + ResultCode.SYNC_CONFLICT -> + res.getString(R.string.uploader_upload_failed_sync_conflict_error_content, operation.fileName) + + else -> null + } + } + + private fun getMessageForResult(result: RemoteOperationResult<*>, res: Resources): String? { + if (result.isSuccess) return null + + val messageRes = if (result.code == ResultCode.TIMEOUT) { + getTimeoutMessageRes(result.exception) + } else { + failedResultMessages[result.code] ?: return result.httpPhrase?.takeIf { it.isNotEmpty() } + } + + return res.getString(messageRes) + } + + @StringRes + private fun getTimeoutMessageRes(exception: Exception?): Int = when (exception) { + is SocketTimeoutException -> R.string.network_error_socket_timeout_exception + is ConnectTimeoutException -> R.string.network_error_connect_timeout_exception + else -> R.string.network_error_socket_exception + } + + private fun getMessageForOperation(operation: RemoteOperation<*>?, res: Resources): String? = when (operation) { + is UploadFileOperation -> res.getString(R.string.uploader_upload_failed_content_single, operation.fileName) + is DownloadFileOperation -> res.getString(R.string.downloader_download_failed_content, operation.savedFileName) + is SynchronizeFolderOperation -> getSyncFolderFailedMessage(operation, res) + else -> getOperationFailedMessageRes(operation)?.let(res::getString) + } + + private fun getSyncFolderFailedMessage(operation: SynchronizeFolderOperation, res: Resources): String? = + operation.folderNameFromPath.getOrNull()?.let { res.getString(R.string.sync_folder_failed_content, it) } + + @StringRes + private fun getOperationFailedMessageRes(operation: RemoteOperation<*>?): Int? = when (operation) { + is RemoveFileOperation -> R.string.remove_fail_msg + is RenameFileOperation -> R.string.rename_server_fail_msg + is CreateFolderOperation -> R.string.create_dir_fail_msg + is CreateShareViaLinkOperation, is CreateShareWithShareeOperation -> R.string.share_link_file_error + is UnshareOperation -> R.string.unshare_link_file_error + is UpdateShareViaLinkOperation, is UpdateSharePermissionsOperation -> R.string.update_link_file_error + is MoveFileOperation -> R.string.move_file_error + is CopyFileOperation -> R.string.copy_file_error + else -> null + } + + private fun getCopyToAlbumMessage(result: RemoteOperationResult<*>, res: Resources): String? = + if (result.code == ResultCode.CONFLICT) res.getString(R.string.album_copy_file_conflict) else null + + private fun getRenameAlbumMessage(result: RemoteOperationResult<*>, res: Resources): String? = + if (result.code == ResultCode.INVALID_OVERWRITE) res.getString(R.string.album_rename_conflict) else null + + private val RemoteOperationResult<*>.shareApiMessage: String? + get() = message?.takeIf { it.isNotEmpty() } + + private val DownloadFileOperation.savedFileName: String + get() = File(savePath).name + + private fun Resources.getForbiddenMessage(@StringRes actionRes: Int): String = + getString(R.string.forbidden_permissions, getString(actionRes)) + + private fun Resources.getServerForbiddenCharactersMessage(): String = + getString(R.string.filename_forbidden_charaters_from_server) +}