Эх сурвалжийг харах

Merge pull request #13583 from nextcloud/feature/auto-rename

Feature - Auto Rename
Tobias Kaminsky 8 сар өмнө
parent
commit
348bc9e0d2

+ 161 - 0
app/src/androidTest/java/com/nextcloud/utils/AutoRenameTests.kt

@@ -0,0 +1,161 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.utils
+
+import com.nextcloud.utils.autoRename.AutoRename
+import com.owncloud.android.AbstractOnServerIT
+import com.owncloud.android.lib.resources.status.NextcloudVersion
+import com.owncloud.android.lib.resources.status.OCCapability
+import org.junit.Before
+import org.junit.Test
+
+@Suppress("TooManyFunctions")
+class AutoRenameTests : AbstractOnServerIT() {
+
+    private var capability: OCCapability = fileDataStorageManager.getCapability(account.name)
+    private val forbiddenFilenameExtension = "."
+    private val forbiddenFilenameCharacter = ">"
+
+    @Before
+    fun setup() {
+        testOnlyOnServer(NextcloudVersion.nextcloud_30)
+
+        capability = capability.apply {
+            forbiddenFilenameExtensionJson = """[" ",".",".part",".part"]"""
+            forbiddenFilenameCharactersJson = """["<", ">", ":", "\\\\", "/", "|", "?", "*", "&"]"""
+        }
+    }
+
+    @Test
+    fun testInvalidChar() {
+        val filename = "file${forbiddenFilenameCharacter}file.txt"
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "file_file.txt"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testInvalidExtension() {
+        val filename = "file$forbiddenFilenameExtension"
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "file_"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testMultipleInvalidChars() {
+        val filename = "file|name?<>.txt"
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "file_name___.txt"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testStartEndInvalidExtensions() {
+        val filename = " .file.part "
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "_file_part"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testStartInvalidExtension() {
+        val filename = " .file.part"
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "_file_part"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testEndInvalidExtension() {
+        val filename = ".file.part "
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "_file_part"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testMiddleNonPrintableChar() {
+        val filename = "file\u0001name.txt"
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "filename.txt"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testStartNonPrintableChar() {
+        val filename = "\u0001filename.txt"
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "filename.txt"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testEndNonPrintableChar() {
+        val filename = "filename.txt\u0001"
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "filename.txt"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testExtensionNonPrintableChar() {
+        val filename = "filename.t\u0001xt"
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "filename.txt"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testMiddleInvalidFolderChar() {
+        val folderPath = "abc/def/kg$forbiddenFilenameCharacter/lmo/pp"
+        val result = AutoRename.rename(folderPath, capability, true)
+        val expectedFolderName = "abc/def/kg_/lmo/pp"
+        assert(result == expectedFolderName) { "Expected $expectedFolderName but got $result" }
+    }
+
+    @Test
+    fun testEndInvalidFolderChar() {
+        val folderPath = "abc/def/kg/lmo/pp$forbiddenFilenameCharacter"
+        val result = AutoRename.rename(folderPath, capability, true)
+        val expectedFolderName = "abc/def/kg/lmo/pp_"
+        assert(result == expectedFolderName) { "Expected $expectedFolderName but got $result" }
+    }
+
+    @Test
+    fun testStartInvalidFolderChar() {
+        val folderPath = "${forbiddenFilenameCharacter}abc/def/kg/lmo/pp"
+        val result = AutoRename.rename(folderPath, capability, true)
+        val expectedFolderName = "_abc/def/kg/lmo/pp"
+        assert(result == expectedFolderName) { "Expected $expectedFolderName but got $result" }
+    }
+
+    @Test
+    fun testMixedInvalidChar() {
+        val filename = " file\u0001na${forbiddenFilenameCharacter}me.txt "
+        val result = AutoRename.rename(filename, capability)
+        val expectedFilename = "filena_me.txt"
+        assert(result == expectedFilename) { "Expected $expectedFilename but got $result" }
+    }
+
+    @Test
+    fun testStartsWithPathSeparator() {
+        val folderPath = "/abc/def/kg/lmo/pp$forbiddenFilenameCharacter/file.txt/"
+        val result = AutoRename.rename(folderPath, capability, true)
+        val expectedFolderName = "/abc/def/kg/lmo/pp_/file.txt/"
+        assert(result == expectedFolderName) { "Expected $expectedFolderName but got $result" }
+    }
+
+    @Test
+    fun testStartsWithPathSeparatorAndValidFilepath() {
+        val folderPath = "/COm02/2569.webp"
+        val result = AutoRename.rename(folderPath, capability, true)
+        val expectedFolderName = "/COm02/2569.webp"
+        assert(result == expectedFolderName) { "Expected $expectedFolderName but got $result" }
+    }
+}

+ 87 - 0
app/src/main/java/com/nextcloud/utils/autoRename/AutoRename.kt

@@ -0,0 +1,87 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.utils.autoRename
+
+import com.nextcloud.utils.extensions.StringConstants
+import com.nextcloud.utils.extensions.forbiddenFilenameCharacters
+import com.nextcloud.utils.extensions.forbiddenFilenameExtension
+import com.nextcloud.utils.extensions.shouldRemoveNonPrintableUnicodeCharactersAndConvertToUTF8
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.lib.resources.status.NextcloudVersion
+import com.owncloud.android.lib.resources.status.OCCapability
+import java.util.regex.Pattern
+
+object AutoRename {
+    private const val REPLACEMENT = "_"
+
+    @Suppress("NestedBlockDepth")
+    fun rename(filename: String, capability: OCCapability, isFolderPath: Boolean = false): String {
+        if (!capability.version.isNewerOrEqual(NextcloudVersion.nextcloud_30)) {
+            return filename
+        }
+
+        val pathSegments = filename.split(OCFile.PATH_SEPARATOR).toMutableList()
+
+        capability.run {
+            forbiddenFilenameCharactersJson?.let {
+                var forbiddenFilenameCharacters = capability.forbiddenFilenameCharacters()
+                if (isFolderPath) {
+                    forbiddenFilenameCharacters = forbiddenFilenameCharacters.filter { it != OCFile.PATH_SEPARATOR }
+                }
+
+                pathSegments.replaceAll { segment ->
+                    var modifiedSegment = segment
+                    forbiddenFilenameCharacters.forEach { forbiddenChar ->
+                        if (modifiedSegment.contains(forbiddenChar)) {
+                            modifiedSegment = modifiedSegment.replace(forbiddenChar, REPLACEMENT)
+                        }
+                    }
+                    modifiedSegment
+                }
+            }
+
+            forbiddenFilenameExtensionJson?.let {
+                forbiddenFilenameExtension().forEach { forbiddenExtension ->
+                    pathSegments.replaceAll { segment ->
+                        var modifiedSegment = segment
+                        if (forbiddenExtension == StringConstants.SPACE) {
+                            modifiedSegment = modifiedSegment.trim()
+                        }
+
+                        if (modifiedSegment.endsWith(forbiddenExtension, ignoreCase = true) ||
+                            modifiedSegment.startsWith(forbiddenExtension, ignoreCase = true)
+                        ) {
+                            modifiedSegment = modifiedSegment.replace(forbiddenExtension, REPLACEMENT)
+                        }
+
+                        modifiedSegment
+                    }
+                }
+            }
+        }
+
+        val result = pathSegments.joinToString(OCFile.PATH_SEPARATOR)
+        return if (capability.shouldRemoveNonPrintableUnicodeCharactersAndConvertToUTF8()) {
+            val utf8Result = convertToUTF8(result)
+            removeNonPrintableUnicodeCharacters(utf8Result)
+        } else {
+            result
+        }
+    }
+
+    private fun convertToUTF8(filename: String): String {
+        return String(filename.toByteArray(), Charsets.UTF_8)
+    }
+
+    private fun removeNonPrintableUnicodeCharacters(filename: String): String {
+        val regex = "\\p{C}"
+        val pattern = Pattern.compile(regex)
+        val matcher = pattern.matcher(filename)
+        return matcher.replaceAll("")
+    }
+}

+ 7 - 0
app/src/main/java/com/nextcloud/utils/extensions/OCCapabilityExtensions.kt

@@ -21,6 +21,13 @@ fun OCCapability.forbiddenFilenameExtensions(): List<String> = jsonToList(forbid
 
 fun OCCapability.forbiddenFilenameBaseNames(): List<String> = jsonToList(forbiddenFilenameBaseNamesJson)
 
+fun OCCapability.shouldRemoveNonPrintableUnicodeCharactersAndConvertToUTF8(): Boolean {
+    return forbiddenFilenames().isNotEmpty() ||
+        forbiddenFilenameCharacters().isNotEmpty() ||
+        forbiddenFilenameExtension().isNotEmpty() ||
+        forbiddenFilenameBaseNames().isNotEmpty()
+}
+
 @Suppress("ReturnCount")
 private fun jsonToList(json: String?): List<String> {
     if (json == null) return emptyList()

+ 19 - 0
app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java

@@ -27,6 +27,7 @@ import com.nextcloud.client.account.CurrentAccountProvider;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.nextcloud.client.jobs.upload.FileUploadWorker;
+import com.nextcloud.utils.autoRename.AutoRename;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
@@ -34,7 +35,9 @@ import com.owncloud.android.db.UploadResult;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.utils.theme.CapabilityUtils;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -65,6 +68,7 @@ public class UploadsStorageManager extends Observable {
 
     private final ContentResolver contentResolver;
     private final CurrentAccountProvider currentAccountProvider;
+    private OCCapability capability;
 
     public UploadsStorageManager(
         CurrentAccountProvider currentAccountProvider,
@@ -77,6 +81,14 @@ public class UploadsStorageManager extends Observable {
         this.currentAccountProvider = currentAccountProvider;
     }
 
+    private void initOCCapability() {
+        try {
+            this.capability = CapabilityUtils.getCapability(MainApp.getAppContext());
+        } catch (RuntimeException e) {
+            Log_OC.e(TAG,"Failed to set OCCapability: Dependencies are not yet ready.");
+        }
+    }
+
     /**
      * Stores an upload object in DB.
      *
@@ -570,10 +582,17 @@ public class UploadsStorageManager extends Observable {
     }
 
     private OCUpload createOCUploadFromCursor(Cursor c) {
+        initOCCapability();
+
         OCUpload upload = null;
         if (c != null) {
             String localPath = c.getString(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_LOCAL_PATH));
+
             String remotePath = c.getString(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_REMOTE_PATH));
+            if (capability != null) {
+                remotePath = AutoRename.INSTANCE.rename(remotePath, capability,true);
+            }
+
             String accountName = c.getString(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_ACCOUNT_NAME));
             upload = new OCUpload(localPath, remotePath, accountName);
 

+ 8 - 2
app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -23,6 +23,7 @@ import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.network.Connectivity;
 import com.nextcloud.client.network.ConnectivityService;
+import com.nextcloud.utils.autoRename.AutoRename;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
 import com.owncloud.android.datamodel.FileDataStorageManager;
@@ -52,6 +53,7 @@ import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation;
 import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation;
 import com.owncloud.android.lib.resources.files.model.RemoteFile;
 import com.owncloud.android.lib.resources.status.E2EVersion;
+import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.operations.common.SyncOperation;
 import com.owncloud.android.operations.e2e.E2EClientData;
 import com.owncloud.android.operations.e2e.E2EData;
@@ -410,8 +412,8 @@ public class UploadFileOperation extends SyncOperation {
         updateSize(0);
 
         String remoteParentPath = new File(getRemotePath()).getParent();
-        remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
-            remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
+        remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
+        remoteParentPath = AutoRename.INSTANCE.rename(remoteParentPath, getCapabilities(), true);
 
         OCFile parent = getStorageManager().getFileByPath(remoteParentPath);
 
@@ -1253,6 +1255,10 @@ public class UploadFileOperation extends SyncOperation {
         }
     }
 
+    private OCCapability getCapabilities() {
+        return CapabilityUtils.getCapability(mContext);
+    }
+
     /**
      * Checks the existence of the folder where the current file will be uploaded both in the remote server and in the
      * local database.

+ 0 - 10
app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java

@@ -675,12 +675,6 @@ public class ReceiveExternalFilesActivity extends FileActivity
             }
             mUploadPath = stringBuilder.toString();
 
-            boolean isPathValid = FileNameValidator.INSTANCE.checkFolderPath(mUploadPath, getCapabilities(), this);
-            if (!isPathValid) {
-                DisplayUtils.showSnackMessage(this, R.string.file_name_validator_error_contains_reserved_names_or_invalid_characters);
-                return;
-            }
-
             if (mUploadFromTmpFile) {
                 DialogInputUploadFilename dialog = DialogInputUploadFilename.newInstance(mSubjectText, mExtraText);
                 dialog.show(getSupportFragmentManager(), null);
@@ -952,10 +946,6 @@ public class ReceiveExternalFilesActivity extends FileActivity
                 messageResTitle = R.string.uploader_error_title_no_file_to_upload;
             } else if (resultCode == UriUploader.UriUploaderResultCode.ERROR_READ_PERMISSION_NOT_GRANTED) {
                 messageResId = R.string.uploader_error_message_read_permission_not_granted;
-            } else if (resultCode == UriUploader.UriUploaderResultCode.ERROR_UNKNOWN) {
-                messageResId = R.string.common_error_unknown;
-            } else if (resultCode == UriUploader.UriUploaderResultCode.INVALID_FILE_NAME) {
-                messageResId = R.string.file_name_validator_upload_content_error;
             }
 
             showErrorDialog(messageResId, messageResTitle);

+ 0 - 19
app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java

@@ -642,12 +642,6 @@ public class UploadFilesActivity extends DrawerActivity implements LocalFileList
                     finish();
                 } else {
                     String[] selectedFilePaths = mFileListFragment.getCheckedFilePaths();
-                    String filenameErrorMessage = checkFileNameBeforeUpload(selectedFilePaths);
-                    if (filenameErrorMessage != null) {
-                        DisplayUtils.showSnackMessage(this, filenameErrorMessage);
-                        return;
-                    }
-
                     boolean isPositionZero = (binding.uploadFilesSpinnerBehaviour.getSelectedItemPosition() == 0);
                     new CheckAvailableSpaceTask(this, selectedFilePaths).execute(isPositionZero);
                 }
@@ -657,19 +651,6 @@ public class UploadFilesActivity extends DrawerActivity implements LocalFileList
         }
     }
 
-    private String checkFileNameBeforeUpload(String[] selectedFilePaths) {
-        for (String filePath : selectedFilePaths) {
-            File file = new File(filePath);
-            String filenameErrorMessage = FileNameValidator.INSTANCE.checkFileName(file.getName(), getCapabilities(), this, null);
-
-            if (filenameErrorMessage != null) {
-                return filenameErrorMessage;
-            }
-        }
-
-        return null;
-    }
-
     @Override
     public void onConfirmation(String callerTag) {
         Log_OC.d(TAG, "Positive button in dialog was clicked; dialog tag is " + callerTag);

+ 38 - 29
app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java

@@ -485,9 +485,8 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
             && fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId() != null &&
             item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) {
             // Thumbnail in Cache?
-            Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
-                String.valueOf(fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId())
-                                                                            );
+            Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(String.valueOf(fakeFileToCheatThumbnailsCacheManagerInterface.getRemoteId()));
+
             if (thumbnail != null && !fakeFileToCheatThumbnailsCacheManagerInterface.isUpdateThumbnailNeeded()) {
                 itemViewHolder.binding.thumbnail.setImageBitmap(thumbnail);
             } else {
@@ -520,47 +519,51 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
             }
 
             if ("image/png".equals(item.getMimeType())) {
-                itemViewHolder.binding.thumbnail.setBackgroundColor(parentActivity.getResources()
-                                                                        .getColor(R.color.bg_default));
+                itemViewHolder.binding.thumbnail.setBackgroundColor(parentActivity.getResources().getColor(R.color.bg_default));
             }
-
-
         } else if (MimeTypeUtil.isImage(fakeFileToCheatThumbnailsCacheManagerInterface)) {
             File file = new File(item.getLocalPath());
-            // Thumbnail in Cache?
-            Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
-                String.valueOf(file.hashCode()));
+            Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(String.valueOf(file.hashCode()));
+
             if (thumbnail != null) {
                 itemViewHolder.binding.thumbnail.setImageBitmap(thumbnail);
-            } else {
-                // generate new Thumbnail
-                if (allowedToCreateNewThumbnail) {
-                    final ThumbnailsCacheManager.ThumbnailGenerationTask task =
-                        new ThumbnailsCacheManager.ThumbnailGenerationTask(itemViewHolder.binding.thumbnail);
+            } else if (allowedToCreateNewThumbnail) {
+                getThumbnailFromFileTypeAndSetIcon(item.getLocalPath(), itemViewHolder);
+
+                final ThumbnailsCacheManager.ThumbnailGenerationTask task =
+                    new ThumbnailsCacheManager.ThumbnailGenerationTask(itemViewHolder.binding.thumbnail);
+
+                if (MimeTypeUtil.isVideo(file)) {
+                    thumbnail = ThumbnailsCacheManager.mDefaultVideo;
+                } else {
+                    thumbnail = ThumbnailsCacheManager.mDefaultImg;
+                }
+
+                final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable =
+                    new ThumbnailsCacheManager.AsyncThumbnailDrawable(parentActivity.getResources(), thumbnail,
+                                                                      task);
 
-                    if (MimeTypeUtil.isVideo(file)) {
-                        thumbnail = ThumbnailsCacheManager.mDefaultVideo;
-                    } else {
-                        thumbnail = ThumbnailsCacheManager.mDefaultImg;
+                task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, null));
+                task.setListener(new ThumbnailsCacheManager.ThumbnailGenerationTask.Listener() {
+                    @Override
+                    public void onSuccess() {
+                        itemViewHolder.binding.thumbnail.setImageDrawable(asyncDrawable);
                     }
 
-                    final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable =
-                        new ThumbnailsCacheManager.AsyncThumbnailDrawable(parentActivity.getResources(), thumbnail,
-                                                                          task);
+                    @Override
+                    public void onError() {
+                        getThumbnailFromFileTypeAndSetIcon(item.getLocalPath(), itemViewHolder);
+                    }
+                });
 
-                    itemViewHolder.binding.thumbnail.setImageDrawable(asyncDrawable);
-                    task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, null));
-                    Log_OC.v(TAG, "Executing task to generate a new thumbnail");
-                }
+                Log_OC.v(TAG, "Executing task to generate a new thumbnail");
             }
 
             if ("image/png".equalsIgnoreCase(item.getMimeType())) {
-                itemViewHolder.binding.thumbnail.setBackgroundColor(parentActivity.getResources()
-                                                                        .getColor(R.color.bg_default));
+                itemViewHolder.binding.thumbnail.setBackgroundColor(parentActivity.getResources().getColor(R.color.bg_default));
             }
         } else {
             if (optionalUser.isPresent()) {
-                final User user = optionalUser.get();
                 final Drawable icon = MimeTypeUtil.getFileTypeIcon(item.getMimeType(),
                                                                    fileName,
                                                                    parentActivity,
@@ -570,6 +573,12 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         }
     }
 
+    private void getThumbnailFromFileTypeAndSetIcon(String localPath, ItemViewHolder itemViewHolder) {
+        Drawable drawable = MimeTypeUtil.getIcon(localPath, parentActivity, viewThemeUtils);
+        if (drawable == null) return;
+        itemViewHolder.binding.thumbnail.setImageDrawable(drawable);
+    }
+
     private boolean checkAndOpenConflictResolutionDialog(User user,
                                                          ItemViewHolder itemViewHolder,
                                                          OCUpload item,

+ 2 - 24
app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt

@@ -15,7 +15,6 @@ import android.net.Uri
 import android.os.Parcelable
 import com.nextcloud.client.account.User
 import com.nextcloud.client.jobs.upload.FileUploadHelper
-import com.nextcloud.utils.fileNameValidator.FileNameValidator
 import com.owncloud.android.R
 import com.owncloud.android.files.services.NameCollisionPolicy
 import com.owncloud.android.lib.common.utils.Log_OC
@@ -25,7 +24,6 @@ import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask
 import com.owncloud.android.ui.asynctasks.CopyAndUploadContentUrisTask.OnCopyTmpFilesTaskListener
 import com.owncloud.android.ui.fragment.TaskRetainerFragment
 import com.owncloud.android.utils.UriUtils.getDisplayNameForUri
-import java.io.File
 
 /**
  * This class examines URIs pointing to files to upload and then requests [FileUploadHelper] to upload them.
@@ -59,8 +57,7 @@ class UriUploader(
         ERROR_UNKNOWN,
         ERROR_NO_FILE_TO_UPLOAD,
         ERROR_READ_PERMISSION_NOT_GRANTED,
-        ERROR_SENSITIVE_PATH,
-        INVALID_FILE_NAME
+        ERROR_SENSITIVE_PATH
     }
 
     @Suppress("NestedBlockDepth")
@@ -74,25 +71,10 @@ class UriUploader(
                 Log_OC.e(TAG, "Sensitive URI detected, aborting upload.")
                 code = UriUploaderResultCode.ERROR_SENSITIVE_PATH
             } else {
-                var isFilenameValid = true
-
                 val uris = mUrisToUpload
                     .filterNotNull()
                     .map { it as Uri }
                     .map { Pair(it, getRemotePathForUri(it)) }
-                    .filter { (_, path) ->
-                        val file = File(path)
-                        val filename = file.name
-
-                        isFilenameValid = FileNameValidator.checkFileName(
-                            filename,
-                            mActivity.capabilities,
-                            mActivity,
-                            null
-                        ) == null
-
-                        isFilenameValid
-                    }
 
                 val fileUris = uris
                     .filter { it.first.scheme == ContentResolver.SCHEME_FILE }
@@ -107,11 +89,7 @@ class UriUploader(
                     val (contentUris, contentRemotePaths) = contentUrisNew.unzip()
                     copyThenUpload(contentUris.toTypedArray(), contentRemotePaths.toTypedArray())
                 } else if (fileUris.isEmpty()) {
-                    code = if (!isFilenameValid) {
-                        UriUploaderResultCode.INVALID_FILE_NAME
-                    } else {
-                        UriUploaderResultCode.ERROR_NO_FILE_TO_UPLOAD
-                    }
+                    code = UriUploaderResultCode.ERROR_NO_FILE_TO_UPLOAD
                 }
             }
         } catch (e: SecurityException) {

+ 1 - 0
app/src/main/java/com/owncloud/android/utils/BitmapUtils.java

@@ -78,6 +78,7 @@ public final class BitmapUtils {
         // make a false load of the bitmap to get its dimensions
         options.inJustDecodeBounds = true;
 
+        // FIXME after auto-rename can't generate thumbnail from localPath
         BitmapFactory.decodeFile(srcPath, options);
 
         // calculate factor to subsample the bitmap

+ 6 - 0
app/src/main/java/com/owncloud/android/utils/EncryptionUtilsV2.kt

@@ -12,6 +12,7 @@ import android.content.Context
 import androidx.annotation.VisibleForTesting
 import com.google.gson.reflect.TypeToken
 import com.nextcloud.client.account.User
+import com.nextcloud.utils.autoRename.AutoRename
 import com.owncloud.android.MainApp
 import com.owncloud.android.R
 import com.owncloud.android.datamodel.ArbitraryDataProvider
@@ -674,6 +675,11 @@ class EncryptionUtilsV2 {
         //     throw IllegalStateException("Metadata is corrupt!")
         // }
 
+        // Auto rename if oc capability enabled for windows compatibility
+        decryptedFolderMetadata.metadata.files.values.forEach { file ->
+            file.filename = AutoRename.rename(file.filename, storageManager.getCapability(user))
+        }
+
         return decryptedFolderMetadata
 
         // handle filesDrops

+ 17 - 0
app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java

@@ -106,6 +106,23 @@ public final class MimeTypeUtil {
         }
     }
 
+    public static Drawable getIcon(String localPath, Context context, ViewThemeUtils viewThemeUtils) {
+        if (context == null) return null;
+
+        String mimeType = getMimeTypeFromPath(localPath);
+        List<String> possibleMimeTypes = Collections.singletonList(mimeType);
+        int iconId = determineIconIdByMimeTypeList(possibleMimeTypes);
+
+        Drawable result = ContextCompat.getDrawable(context, iconId);
+        if (result == null) return null;
+
+        if (R.drawable.file_zip == iconId) {
+            viewThemeUtils.platform.tintDrawable(context, result, ColorRole.PRIMARY);
+        }
+
+        return result;
+    }
+
     /**
      * Returns the resource identifier of an image to use as icon associated to a known MIME type.
      *

+ 0 - 1
app/src/main/res/values/strings.xml

@@ -1235,7 +1235,6 @@
     <string name="unified_search_fragment_permission_needed">Permissions are required to open search result otherwise it will redirected to web…</string>
     <string name="file_name_validator_current_path_is_invalid">Current folder name is invalid, please rename the folder. Redirecting home…</string>
     <string name="file_name_validator_rename_before_move_or_copy">%s. Please rename the file before moving or copying</string>
-    <string name="file_name_validator_upload_content_error">Some content cannot be uploaded since it contains reserved names or invalid characters</string>
     <string name="file_name_validator_error_contains_reserved_names_or_invalid_characters">Folder path contains reserved names or invalid characters</string>
     <string name="file_name_validator_error_invalid_character">Name contains invalid characters: %s</string>
     <string name="file_name_validator_error_reserved_names">%s is a forbidden name</string>