From a53aef36b6d2230fe6f5d6a5658bc64149aab2e2 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 11 Jul 2023 17:54:37 +0200 Subject: [PATCH 01/10] fix all warning --- .../foundation/e/drive/utils/CommonUtils.java | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java index 1ffb3a1e..2334d009 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -123,7 +123,7 @@ public abstract class CommonUtils { * * @param account Account used for synchronisation * @param syncedFileStateIsMedia true if the concerned syncedFileState is a media's type element, false if it is a settings's type element - * @return + * @return true if sync is allowed in given condition */ public static boolean isThisSyncAllowed(@NonNull Account account, boolean syncedFileStateIsMedia) { return (syncedFileStateIsMedia && isMediaSyncEnabled(account)) @@ -152,15 +152,15 @@ public abstract class CommonUtils { /** * Read accountManager settings - * - * @param account + * to check if user can sync on metered network + * @param account account used to check settings * @return true if usage of metered connection is allowed */ public static boolean isMeteredNetworkAllowed(@NonNull Account account) { return ContentResolver.getSyncAutomatically(account, METERED_NETWORK_ALLOWED_AUTHORITY); } - /** methods relative to file **/ + /* methods relative to file */ /** * Return name of a file from its access path @@ -188,14 +188,11 @@ public abstract class CommonUtils { final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); final NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); - if (capabilities != null + return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) && (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) - || meteredNetworkAllowed)) { - return true; - } - return false; + || meteredNetworkAllowed); } @@ -217,7 +214,7 @@ public abstract class CommonUtils { } /** - * Update Gallery for showing the file in paramater + * Update Gallery for showing the file in parameter * * @param context Calling service context * @param filePath String containing path the file to update by mediaScanner @@ -229,13 +226,8 @@ public abstract class CommonUtils { MediaScannerConnection.scanFile(context, filePathArray, mimeType, - new MediaScannerConnection.OnScanCompletedListener() { - @Override - public void onScanCompleted(String path, Uri uri) { - Timber.tag("MediaScannerConnection") - .v("file %s was scanned with success: %s ", path, uri); - } - }); + (path, uri) -> Timber.tag("MediaScannerConnection") + .v("file %s was scanned with success: %s ", path, uri)); } /** @@ -258,7 +250,7 @@ public abstract class CommonUtils { /** * Formatter class is not used since bytes passed by server are in SI unit aka 1kb = 1024byte - * https://stackoverflow.com/questions/3758606/how-can-i-convert-byte-size-into-a-human-readable-format-in-java/3758880#3758880 + * ... * * @param bytes file/data size in bytes * @return String in human readable format @@ -342,11 +334,11 @@ public abstract class CommonUtils { /** * Job for Widget & notification about quota * - * @param workManager + * @param workManager component used to register worker */ public static void registerPeriodicUserInfoChecking(@NonNull WorkManager workManager) { final PeriodicWorkRequest workRequest = WorkRequestFactory.getPeriodicWorkRequest(WorkRequestFactory.WorkType.PERIODIC_USER_INFO); - workManager.enqueueUniquePeriodicWork(AccountUserInfoWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.REPLACE, workRequest); + workManager.enqueueUniquePeriodicWork(AccountUserInfoWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.UPDATE, workRequest); } /** -- GitLab From 8f52ec4dc381ab07374f1899b69d9869313e6c37 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 11 Jul 2023 18:16:26 +0200 Subject: [PATCH 02/10] split CommonUtils: move File's related code into new class 'FileUtils.java' --- .../FileObservers/FileEventListener.java | 17 ++--- .../contentScanner/LocalContentScanner.java | 3 +- .../contentScanner/RemoteContentScanner.java | 4 +- .../contentScanner/RemoteFileLister.java | 8 +-- .../operations/DownloadFileOperation.java | 23 +++++- .../drive/operations/UploadFileOperation.java | 6 +- .../foundation/e/drive/utils/CommonUtils.java | 69 +----------------- .../foundation/e/drive/utils/FileUtils.java | 71 +++++++++++++++++++ .../e/drive/utils/RootSyncedFolderProvider.kt | 4 +- 9 files changed, 116 insertions(+), 89 deletions(-) create mode 100644 app/src/main/java/foundation/e/drive/utils/FileUtils.java diff --git a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java index 5f9a07f4..0b64f1c9 100644 --- a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +++ b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java @@ -13,6 +13,7 @@ import static foundation.e.drive.models.SyncRequest.Type.UPLOAD; import static foundation.e.drive.models.SyncedFileState.DEVICE_SCANNABLE; import static foundation.e.drive.models.SyncedFileState.ECLOUD_SCANNABLE; import static foundation.e.drive.models.SyncedFileState.NOT_SCANNABLE; +import static foundation.e.drive.utils.FileUtils.*; import android.content.Context; import android.os.FileObserver; @@ -125,7 +126,7 @@ public class FileEventListener { */ private void handleDirectoryCreate(@NonNull File directory) { Timber.d("handleDirectoryCreate( %s )",directory.getAbsolutePath()); - final String parentPath = CommonUtils.getLocalPath(directory.getParentFile()); + final String parentPath = getLocalPath(directory.getParentFile()); final SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder != null) { final SyncedFolder folder = new SyncedFolder(parentFolder, directory.getName() + FileUtils.PATH_SEPARATOR, directory.lastModified(), ""); @@ -139,7 +140,7 @@ public class FileEventListener { * @param directory */ private void handleDirectoryCloseWrite(@NonNull File directory) { - final String fileLocalPath = CommonUtils.getLocalPath(directory); + final String fileLocalPath = getLocalPath(directory); Timber.d("handleDirectoryCloseWrite( %s )",fileLocalPath ); final SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); if (folder == null) { @@ -155,7 +156,7 @@ public class FileEventListener { * @param directory */ private void handleDirectoryDelete(@NonNull File directory) { - final String fileLocalPath = CommonUtils.getLocalPath(directory); + final String fileLocalPath = getLocalPath(directory); Timber.d("handleDirectoryDelete( %s )", fileLocalPath); SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); if (folder == null) { @@ -163,7 +164,7 @@ public class FileEventListener { final File parentFile = directory.getParentFile(); if (parentFile == null) return; - final String parentPath = CommonUtils.getLocalPath(parentFile); + final String parentPath = getLocalPath(parentFile); final SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder == null ) { //if parent is not in the DB return; @@ -182,13 +183,13 @@ public class FileEventListener { * @param file */ private void handleFileCloseWrite(@NonNull File file) { - final String fileLocalPath = CommonUtils.getLocalPath(file); + final String fileLocalPath = getLocalPath(file); Timber.d("handleFileCloseWrite( %s )", fileLocalPath); SyncRequest request = null; SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); if (fileState == null) { //New file discovered - final String parentPath = CommonUtils.getLocalPath(file.getParentFile()); + final String parentPath = getLocalPath(file.getParentFile()); SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder == null || !parentFolder.isEnabled()) { Timber.d("Won't send sync request: no parent are known for new file: %s", file.getName()); @@ -201,7 +202,7 @@ public class FileEventListener { } final String remotePath = parentFolder.getRemoteFolder()+file.getName(); - fileState = new SyncedFileState(-1, file.getName(), CommonUtils.getLocalPath(file), remotePath, "", 0L, parentFolder.getId(), parentFolder.isMediaType(), scannableValue); + fileState = new SyncedFileState(-1, file.getName(), getLocalPath(file), remotePath, "", 0L, parentFolder.getId(), parentFolder.isMediaType(), scannableValue); int storedId = DbHelper.manageSyncedFileStateDB(fileState, "INSERT", appContext); if (storedId > 0) { fileState.setId(storedId); @@ -225,7 +226,7 @@ public class FileEventListener { * @param file */ private void handleFileDelete(@NonNull File file) { - final String fileLocalPath = CommonUtils.getLocalPath(file); + final String fileLocalPath = getLocalPath(file); Timber.d("handleFileDelete( %s )",fileLocalPath); final SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); if (fileState == null) { diff --git a/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java b/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java index 1a9417c7..e8ea6e42 100644 --- a/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java +++ b/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java @@ -24,6 +24,7 @@ import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.FileDiffUtils; +import foundation.e.drive.utils.FileUtils; import timber.log.Timber; /** @@ -92,7 +93,7 @@ public class LocalContentScanner extends AbstractContentScanner{ @Override protected boolean isFileMatchingSyncedFileState(@NonNull File file, @NonNull SyncedFileState fileState) { - final String filePath = CommonUtils.getLocalPath(file); + final String filePath = FileUtils.getLocalPath(file); final String localPath = fileState.getLocalPath(); return localPath != null && localPath.equals(filePath); diff --git a/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java b/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java index 25b008ad..3a7d500f 100644 --- a/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java +++ b/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java @@ -26,8 +26,8 @@ import foundation.e.drive.models.DownloadRequest; import foundation.e.drive.models.SyncRequest; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; -import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.FileDiffUtils; +import foundation.e.drive.utils.FileUtils; import timber.log.Timber; /** @@ -69,7 +69,7 @@ public class RemoteContentScanner extends AbstractContentScanner { final SyncedFolder parentDir = getParentSyncedFolder(remoteFilePath); if (parentDir == null) return; - final String fileName = CommonUtils.getFileNameFromPath(remoteFilePath); + final String fileName = FileUtils.getFileNameFromPath(remoteFilePath); int scannableValue = NOT_SCANNABLE; if (parentDir.isEnabled()) { diff --git a/app/src/main/java/foundation/e/drive/contentScanner/RemoteFileLister.java b/app/src/main/java/foundation/e/drive/contentScanner/RemoteFileLister.java index f8ae821b..8c54a649 100644 --- a/app/src/main/java/foundation/e/drive/contentScanner/RemoteFileLister.java +++ b/app/src/main/java/foundation/e/drive/contentScanner/RemoteFileLister.java @@ -23,7 +23,7 @@ import java.util.List; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFolder; -import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.FileUtils; /** * Implementation of AbstractFileLister with method adapted to remote content @@ -42,13 +42,13 @@ public class RemoteFileLister extends AbstractFileLister { @Override protected boolean skipSyncedFolder(@NonNull SyncedFolder folder) { return (folder.isMediaType() - && CommonUtils.getFileNameFromPath(folder.getRemoteFolder()).startsWith(".")) + && FileUtils.getFileNameFromPath(folder.getRemoteFolder()).startsWith(".")) || !folder.isScanRemote(); } @Override protected boolean skipFile(@NonNull RemoteFile file) { - final String fileName = CommonUtils.getFileNameFromPath(file.getRemotePath()); + final String fileName = FileUtils.getFileNameFromPath(file.getRemotePath()); return fileName == null || fileName.isEmpty() || fileName.startsWith("."); } @@ -66,7 +66,7 @@ public class RemoteFileLister extends AbstractFileLister { @Override @Nullable protected String getFileName(@NonNull RemoteFile file) { - return CommonUtils.getFileNameFromPath(file.getRemotePath()); + return FileUtils.getFileNameFromPath(file.getRemotePath()); } @Override diff --git a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java index 11c1dfd1..ed7f35d4 100644 --- a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java @@ -19,6 +19,7 @@ import static com.owncloud.android.lib.common.operations.RemoteOperationResult.R import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import android.content.Context; +import android.media.MediaScannerConnection; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -35,7 +36,7 @@ import java.nio.file.Path; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFileState; -import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.utils.FileUtils; import timber.log.Timber; /** @@ -155,7 +156,7 @@ public class DownloadFileOperation extends RemoteOperation { syncedFileState.setLocalLastModified(targetFile.lastModified()) .setLastETAG(remoteFile.getEtag()); - CommonUtils.doActionMediaScannerConnexionScanFile(context, syncedFileState.getLocalPath()); //required to make Gallery show new image + doActionMediaScannerConnexionScanFile(context, syncedFileState.getLocalPath()); //required to make Gallery show new image return OK; } @@ -190,4 +191,22 @@ public class DownloadFileOperation extends RemoteOperation { public String getRemoteFilePath() { return remoteFile.getRemotePath(); } + + + /** + * Update Gallery for showing the file in parameter + * + * @param context Calling service context + * @param filePath String containing path the file to update by mediaScanner + */ + private void doActionMediaScannerConnexionScanFile(@NonNull Context context, @NonNull final String filePath) { + Timber.v("doActionMediaScannerConnexionScanFile( %s )", filePath); + final String[] filePathArray = new String[] { filePath }; + final String[] mimeType = new String[] {FileUtils.getMimeType(new File(filePath))}; + MediaScannerConnection.scanFile(context, + filePathArray, + mimeType, + (path, uri) -> Timber.tag("MediaScannerConnection") + .v("file %s was scanned with success: %s ", path, uri)); + } } diff --git a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java index 75e09b3f..3900c261 100644 --- a/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java @@ -9,6 +9,8 @@ package foundation.e.drive.operations; +import static foundation.e.drive.utils.FileUtils.getMimeType; + import android.accounts.Account; import android.content.Context; @@ -253,7 +255,7 @@ public class UploadFileOperation extends RemoteOperation { @NonNull public RemoteOperationResult uploadChunkedFile(@NonNull final File file, @NonNull final OwnCloudClient client) { final String timeStamp = formatTimestampToMatchCloud(file.lastModified()); - final String mimeType = CommonUtils.getMimeType(file); + final String mimeType = getMimeType(file); final ChunkedFileUploadRemoteOperation uploadOperation = new ChunkedFileUploadRemoteOperation(syncedState.getLocalPath(), syncedState.getRemotePath(), mimeType, syncedState.getLastETAG(), @@ -311,7 +313,7 @@ public class UploadFileOperation extends RemoteOperation { final UploadFileRemoteOperation uploadOperation = new UploadFileRemoteOperation(syncedState.getLocalPath(), syncedState.getRemotePath(), - CommonUtils.getMimeType(file), + getMimeType(file), eTag, //If not null, This can cause error 412; that means remote file has change timeStamp); return uploadOperation.execute(client); diff --git a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java index 2334d009..66a22985 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -27,10 +27,7 @@ import android.net.Uri; import android.webkit.MimeTypeMap; import android.widget.Toast; -import com.owncloud.android.lib.resources.files.FileUtils; - import java.io.File; -import java.io.IOException; import java.lang.reflect.Method; import java.text.CharacterIterator; import java.text.StringCharacterIterator; @@ -162,18 +159,7 @@ public abstract class CommonUtils { /* methods relative to file */ - /** - * Return name of a file from its access path - * - * @param path File name will be extracted from this path. Do not provide directory path - * @return String, the last part after separator of path or null if invalid path has been provided - */ - @Nullable - public static String getFileNameFromPath(@NonNull String path) { - final String[] splittedString = path.split(FileUtils.PATH_SEPARATOR); - if (splittedString.length <= 0) return null; - return splittedString[splittedString.length - 1]; - } + /** * Tell if there is internet connection @@ -195,59 +181,6 @@ public abstract class CommonUtils { || meteredNetworkAllowed); } - - /** - * Get mimetype of file from the file itself - * - * @param file the file for whom we want Mime type - * @return String containing mimeType of the file - */ - @NonNull - public static String getMimeType(@NonNull File file) { - final String mimetype = MimeTypeMap.getSingleton() - .getMimeTypeFromExtension( - MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()).toLowerCase(Locale.ROOT)); - if (mimetype == null) { - return "*/*"; - } - return mimetype; - } - - /** - * Update Gallery for showing the file in parameter - * - * @param context Calling service context - * @param filePath String containing path the file to update by mediaScanner - */ - public static void doActionMediaScannerConnexionScanFile(@NonNull Context context, @NonNull final String filePath) { - Timber.v("doActionMediaScannerConnexionScanFile( %s )", filePath); - final String[] filePathArray = new String[] { filePath }; - final String[] mimeType = new String[] {getMimeType(new File(filePath))}; - MediaScannerConnection.scanFile(context, - filePathArray, - mimeType, - (path, uri) -> Timber.tag("MediaScannerConnection") - .v("file %s was scanned with success: %s ", path, uri)); - } - - /** - * Return the canonical path if available, either return absolute path - * - * @param file file from which we want the path - * @return canonical path if available - */ - @NonNull - public static String getLocalPath(@NonNull File file) { - String result; - try { - result = file.getCanonicalPath(); - } catch (SecurityException | IOException exception ) { - Timber.v(exception); - result = file.getAbsolutePath(); //todo why not simply always use getAbsolutePath? - } - return result; - } - /** * Formatter class is not used since bytes passed by server are in SI unit aka 1kb = 1024byte * ... diff --git a/app/src/main/java/foundation/e/drive/utils/FileUtils.java b/app/src/main/java/foundation/e/drive/utils/FileUtils.java new file mode 100644 index 00000000..22eb0447 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/utils/FileUtils.java @@ -0,0 +1,71 @@ +/* + * Copyright © ECORP SAS 2023. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ +package foundation.e.drive.utils; + +import android.net.Uri; +import android.webkit.MimeTypeMap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.File; +import java.io.IOException; +import java.util.Locale; + +import timber.log.Timber; + +public abstract class FileUtils { + + /** + * Return name of a file from its access path + * + * @param path File name will be extracted from this path. Do not provide directory path + * @return String, the last part after separator of path or null if invalid path has been provided + */ + @Nullable + public static String getFileNameFromPath(@NonNull String path) { + final String[] splittedString = path.split(com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR); + if (splittedString.length <= 0) return null; + return splittedString[splittedString.length - 1]; + } + + /** + * Return the canonical path if available, either return absolute path + * + * @param file file from which we want the path + * @return canonical path if available + */ + @NonNull + public static String getLocalPath(@NonNull File file) { + String result; + try { + result = file.getCanonicalPath(); + } catch (SecurityException | IOException exception ) { + Timber.v(exception); + result = file.getAbsolutePath(); //todo why not simply always use getAbsolutePath? + } + return result; + } + + /** + * Get mimetype of file from the file itself + * + * @param file the file for whom we want Mime type + * @return String containing mimeType of the file + */ + @NonNull + public static String getMimeType(@NonNull File file) { + final String mimetype = MimeTypeMap.getSingleton() + .getMimeTypeFromExtension( + MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()).toLowerCase(Locale.ROOT)); + if (mimetype == null) { + return "*/*"; + } + return mimetype; + } +} diff --git a/app/src/main/java/foundation/e/drive/utils/RootSyncedFolderProvider.kt b/app/src/main/java/foundation/e/drive/utils/RootSyncedFolderProvider.kt index cd03ed03..5f7295af 100644 --- a/app/src/main/java/foundation/e/drive/utils/RootSyncedFolderProvider.kt +++ b/app/src/main/java/foundation/e/drive/utils/RootSyncedFolderProvider.kt @@ -123,7 +123,7 @@ object RootSyncedFolderProvider { } private fun createMediaSyncedFolder(category: String, publicDirectoryType: String, remotePath: String): SyncedFolder { - val dirPath = CommonUtils.getLocalPath(Environment.getExternalStoragePublicDirectory(publicDirectoryType)) + val dirPath = FileUtils.getLocalPath(Environment.getExternalStoragePublicDirectory(publicDirectoryType)) val localPath = dirPath + PATH_SEPARATOR return SyncedFolder(category, localPath, remotePath, true) } @@ -137,7 +137,7 @@ object RootSyncedFolderProvider { * * @return true or false */ - fun isAboveA12(): Boolean { + private fun isAboveA12(): Boolean { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S } } \ No newline at end of file -- GitLab From 311391694ee156d83355275250270f20ab351616 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 11 Jul 2023 18:18:16 +0200 Subject: [PATCH 03/10] remove useless tag for timber call and useless import --- .../main/java/foundation/e/drive/utils/CommonUtils.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java index 66a22985..92bdef24 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -19,15 +19,12 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; -import android.media.MediaScannerConnection; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.Uri; -import android.webkit.MimeTypeMap; import android.widget.Toast; -import java.io.File; import java.lang.reflect.Method; import java.text.CharacterIterator; import java.text.StringCharacterIterator; @@ -60,7 +57,6 @@ import androidx.work.WorkManager; * @author Mohit Mali */ public abstract class CommonUtils { - private final static String TAG = CommonUtils.class.getSimpleName(); /** * Set ServiceUncaughtExceptionHandler to be the MainThread Exception Handler @@ -169,7 +165,7 @@ public abstract class CommonUtils { * @return True if there is connection, false either */ public static boolean haveNetworkConnection(@NonNull Context context, boolean meteredNetworkAllowed) { - Timber.tag(TAG).v("haveNetworkConnection()"); + Timber.v("haveNetworkConnection()"); final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); final NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); @@ -221,7 +217,7 @@ public abstract class CommonUtils { */ public static void registerInitializationWorkers(@Nullable List syncedFolders, @NonNull WorkManager workManager) { if (syncedFolders == null || syncedFolders.isEmpty()) { - Timber.tag(TAG).d("registerInitializationWorkers: Can't create remote folders. List is empty"); + Timber.d("registerInitializationWorkers: Can't create remote folders. List is empty"); return; } -- GitLab From 186c5a63261cb1e00c0c14d00e98a7f9a3f76b26 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 11 Jul 2023 18:25:46 +0200 Subject: [PATCH 04/10] Convert FileUtils.java into FileUtils.kt and remove remaining useless import of CommonUtils --- .../FileObservers/FileEventListener.java | 9 ++- .../contentScanner/LocalContentScanner.java | 1 - .../e/drive/fileFilters/MediaFileFilter.java | 10 +-- .../foundation/e/drive/utils/FileUtils.java | 71 ------------------- .../foundation/e/drive/utils/FileUtils.kt | 68 ++++++++++++++++++ 5 files changed, 78 insertions(+), 81 deletions(-) delete mode 100644 app/src/main/java/foundation/e/drive/utils/FileUtils.java create mode 100644 app/src/main/java/foundation/e/drive/utils/FileUtils.kt diff --git a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java index 0b64f1c9..6effd873 100644 --- a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +++ b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java @@ -13,14 +13,14 @@ import static foundation.e.drive.models.SyncRequest.Type.UPLOAD; import static foundation.e.drive.models.SyncedFileState.DEVICE_SCANNABLE; import static foundation.e.drive.models.SyncedFileState.ECLOUD_SCANNABLE; import static foundation.e.drive.models.SyncedFileState.NOT_SCANNABLE; -import static foundation.e.drive.utils.FileUtils.*; +import static foundation.e.drive.utils.FileUtils.getLocalPath; import android.content.Context; import android.os.FileObserver; import androidx.annotation.NonNull; -import com.owncloud.android.lib.resources.files.FileUtils; +import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; import java.io.File; @@ -29,7 +29,6 @@ import foundation.e.drive.models.SyncRequest; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.services.SynchronizationService; -import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.SynchronizationServiceConnection; import timber.log.Timber; @@ -129,7 +128,7 @@ public class FileEventListener { final String parentPath = getLocalPath(directory.getParentFile()); final SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); if (parentFolder != null) { - final SyncedFolder folder = new SyncedFolder(parentFolder, directory.getName() + FileUtils.PATH_SEPARATOR, directory.lastModified(), ""); + final SyncedFolder folder = new SyncedFolder(parentFolder, directory.getName() + PATH_SEPARATOR, directory.lastModified(), ""); DbHelper.insertSyncedFolder(folder, appContext); } } @@ -169,7 +168,7 @@ public class FileEventListener { if (parentFolder == null ) { //if parent is not in the DB return; } - folder = new SyncedFolder(parentFolder, directory.getName()+ FileUtils.PATH_SEPARATOR, directory.lastModified(), ""); + folder = new SyncedFolder(parentFolder, directory.getName()+ PATH_SEPARATOR, directory.lastModified(), ""); folder.setEnabled(false); DbHelper.insertSyncedFolder(folder, appContext); } else if (folder.isEnabled()) { diff --git a/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java b/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java index e8ea6e42..bd637fa6 100644 --- a/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java +++ b/app/src/main/java/foundation/e/drive/contentScanner/LocalContentScanner.java @@ -22,7 +22,6 @@ import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncRequest; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; -import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.FileDiffUtils; import foundation.e.drive.utils.FileUtils; import timber.log.Timber; diff --git a/app/src/main/java/foundation/e/drive/fileFilters/MediaFileFilter.java b/app/src/main/java/foundation/e/drive/fileFilters/MediaFileFilter.java index f99e757b..6ac1740a 100644 --- a/app/src/main/java/foundation/e/drive/fileFilters/MediaFileFilter.java +++ b/app/src/main/java/foundation/e/drive/fileFilters/MediaFileFilter.java @@ -12,16 +12,18 @@ package foundation.e.drive.fileFilters; import java.io.File; import java.io.FileFilter; -import foundation.e.drive.utils.CommonUtils; - /** * @author Vincent Bourgmayer */ class MediaFileFilter implements FileFilter { + /** + * Only accept not hidden files: + * Media should not be synced if they're hidden files + * @param file File to check + * @return true if file is accepted + */ @Override public boolean accept(File file) { - //Return true if it's not a hidden file - return !file.isHidden(); } } diff --git a/app/src/main/java/foundation/e/drive/utils/FileUtils.java b/app/src/main/java/foundation/e/drive/utils/FileUtils.java deleted file mode 100644 index 22eb0447..00000000 --- a/app/src/main/java/foundation/e/drive/utils/FileUtils.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright © ECORP SAS 2023. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ -package foundation.e.drive.utils; - -import android.net.Uri; -import android.webkit.MimeTypeMap; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.File; -import java.io.IOException; -import java.util.Locale; - -import timber.log.Timber; - -public abstract class FileUtils { - - /** - * Return name of a file from its access path - * - * @param path File name will be extracted from this path. Do not provide directory path - * @return String, the last part after separator of path or null if invalid path has been provided - */ - @Nullable - public static String getFileNameFromPath(@NonNull String path) { - final String[] splittedString = path.split(com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR); - if (splittedString.length <= 0) return null; - return splittedString[splittedString.length - 1]; - } - - /** - * Return the canonical path if available, either return absolute path - * - * @param file file from which we want the path - * @return canonical path if available - */ - @NonNull - public static String getLocalPath(@NonNull File file) { - String result; - try { - result = file.getCanonicalPath(); - } catch (SecurityException | IOException exception ) { - Timber.v(exception); - result = file.getAbsolutePath(); //todo why not simply always use getAbsolutePath? - } - return result; - } - - /** - * Get mimetype of file from the file itself - * - * @param file the file for whom we want Mime type - * @return String containing mimeType of the file - */ - @NonNull - public static String getMimeType(@NonNull File file) { - final String mimetype = MimeTypeMap.getSingleton() - .getMimeTypeFromExtension( - MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()).toLowerCase(Locale.ROOT)); - if (mimetype == null) { - return "*/*"; - } - return mimetype; - } -} diff --git a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt new file mode 100644 index 00000000..b48b4409 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt @@ -0,0 +1,68 @@ +/* + * Copyright © ECORP SAS 2023. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ +package foundation.e.drive.utils + +import android.net.Uri +import android.webkit.MimeTypeMap +import com.owncloud.android.lib.resources.files.FileUtils +import timber.log.Timber +import java.io.File +import java.io.IOException + +object FileUtils { + /** + * Return name of a file from its access path + * + * @param path File name will be extracted from this path. Do not provide directory path + * @return String, the last part after separator of path or null if invalid path has been provided + */ + @JvmStatic + fun getFileNameFromPath(path: String): String? { + val splittedString = + path.split(FileUtils.PATH_SEPARATOR.toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray() + return if (splittedString.size <= 0) null else splittedString[splittedString.size - 1] + } + + /** + * Return the canonical path if available, either return absolute path + * + * @param file file from which we want the path + * @return canonical path if available + */ + @JvmStatic + fun getLocalPath(file: File): String { + val result: String + result = try { + file.canonicalPath + } catch (exception: SecurityException) { + Timber.v(exception) + file.absolutePath //todo why not simply always use getAbsolutePath? + } catch (exception: IOException) { + Timber.v(exception) + file.absolutePath + } + return result + } + + /** + * Get mimetype of file from the file itself + * + * @param file the file for whom we want Mime type + * @return String containing mimeType of the file + */ + @JvmStatic + fun getMimeType(file: File): String { + return MimeTypeMap.getSingleton() + .getMimeTypeFromExtension( + MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()) + .lowercase() + ) + ?: return "*/*" + } +} \ No newline at end of file -- GitLab From b5753d6d7cd3f7b12018166e19435469c72d437e Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Wed, 12 Jul 2023 10:13:58 +0200 Subject: [PATCH 05/10] fix bad method name with frenglish & fix one licence header --- .../e/drive/operations/DownloadFileOperation.java | 6 +++--- app/src/main/java/foundation/e/drive/utils/FileUtils.kt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java index ed7f35d4..c5493454 100644 --- a/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/operations/DownloadFileOperation.java @@ -156,7 +156,7 @@ public class DownloadFileOperation extends RemoteOperation { syncedFileState.setLocalLastModified(targetFile.lastModified()) .setLastETAG(remoteFile.getEtag()); - doActionMediaScannerConnexionScanFile(context, syncedFileState.getLocalPath()); //required to make Gallery show new image + doActionMediaScannerConnectionScanFile(context, syncedFileState.getLocalPath()); //required to make Gallery show new image return OK; } @@ -199,8 +199,8 @@ public class DownloadFileOperation extends RemoteOperation { * @param context Calling service context * @param filePath String containing path the file to update by mediaScanner */ - private void doActionMediaScannerConnexionScanFile(@NonNull Context context, @NonNull final String filePath) { - Timber.v("doActionMediaScannerConnexionScanFile( %s )", filePath); + private void doActionMediaScannerConnectionScanFile(@NonNull Context context, @NonNull final String filePath) { + Timber.v("doActionMediaScannerConnectionScanFile( %s )", filePath); final String[] filePathArray = new String[] { filePath }; final String[] mimeType = new String[] {FileUtils.getMimeType(new File(filePath))}; MediaScannerConnection.scanFile(context, diff --git a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt index b48b4409..7d7677e9 100644 --- a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt +++ b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright © ECORP SAS 2023. + * Copyright © MURENA SAS 2023. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at -- GitLab From 420ac684d4e60d15d08edab9cbbc49080a46b3d6 Mon Sep 17 00:00:00 2001 From: Hasib Prince Date: Wed, 12 Jul 2023 08:45:00 +0000 Subject: [PATCH 06/10] Apply 1 suggestion(s) to 1 file(s) --- .../foundation/e/drive/FileObservers/FileEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java index 6effd873..6cec272c 100644 --- a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +++ b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java @@ -168,7 +168,7 @@ public class FileEventListener { if (parentFolder == null ) { //if parent is not in the DB return; } - folder = new SyncedFolder(parentFolder, directory.getName()+ PATH_SEPARATOR, directory.lastModified(), ""); + folder = new SyncedFolder(parentFolder, directory.getName() + PATH_SEPARATOR, directory.lastModified(), ""); folder.setEnabled(false); DbHelper.insertSyncedFolder(folder, appContext); } else if (folder.isEnabled()) { -- GitLab From cdbbdf77f22983a35ff0d6df2fe60fe3c50e5da5 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Wed, 12 Jul 2023 15:12:42 +0200 Subject: [PATCH 07/10] apply Sayantan suggestion: rewrite getFileNameFromPath --- .../main/java/foundation/e/drive/utils/FileUtils.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt index 7d7677e9..a4c9857e 100644 --- a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt +++ b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt @@ -23,10 +23,9 @@ object FileUtils { */ @JvmStatic fun getFileNameFromPath(path: String): String? { - val splittedString = - path.split(FileUtils.PATH_SEPARATOR.toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray() - return if (splittedString.size <= 0) null else splittedString[splittedString.size - 1] + val lastIndexOfPathSeparator = path.lastIndexOf(FileUtils.PATH_SEPARATOR) + if (lastIndexOfPathSeparator == -1) return null + return path.substring(lastIndexOfPathSeparator + 1) } /** @@ -37,8 +36,7 @@ object FileUtils { */ @JvmStatic fun getLocalPath(file: File): String { - val result: String - result = try { + val result: String = try { file.canonicalPath } catch (exception: SecurityException) { Timber.v(exception) -- GitLab From ed0eceefadf1250546e5f04256698c48b7794e77 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Wed, 12 Jul 2023 17:45:49 +0200 Subject: [PATCH 08/10] add unit test --- .../foundation/e/drive/utils/FileUtils.kt | 8 ++-- .../foundation/e/drive/utils/FileUtilsTest.kt | 45 +++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 app/src/test/java/foundation/e/drive/utils/FileUtilsTest.kt diff --git a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt index a4c9857e..109eb482 100644 --- a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt +++ b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt @@ -56,11 +56,11 @@ object FileUtils { */ @JvmStatic fun getMimeType(file: File): String { + val fileExtension: String = MimeTypeMap.getFileExtensionFromUrl(file.toURI().toString()) + ?: return "*/*" + return MimeTypeMap.getSingleton() - .getMimeTypeFromExtension( - MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(file).toString()) - .lowercase() - ) + .getMimeTypeFromExtension(fileExtension.lowercase()) ?: return "*/*" } } \ No newline at end of file diff --git a/app/src/test/java/foundation/e/drive/utils/FileUtilsTest.kt b/app/src/test/java/foundation/e/drive/utils/FileUtilsTest.kt new file mode 100644 index 00000000..f0c1d0d9 --- /dev/null +++ b/app/src/test/java/foundation/e/drive/utils/FileUtilsTest.kt @@ -0,0 +1,45 @@ +package foundation.e.drive.utils + +import android.net.Uri +import org.junit.Assert +import org.junit.Ignore +import org.junit.Test +import java.io.File + +internal class FileUtilsTest { + + @Test + fun `get file name from path with invalid path should return null`() { + val invalidPath = "photo.jpg" + val result = FileUtils.getFileNameFromPath(invalidPath) + Assert.assertNull("Null was expected but got ${result.toString()}", result) + } + + @Test + fun `get file name from path with valid path should return the file name`() { + val expectedResult = "photo.jpg" + val validPath = "folder/photo.jpg" + val result = FileUtils.getFileNameFromPath(validPath) + Assert.assertEquals("Got $result instead of $expectedResult", expectedResult, result) + + val validPath2 = "/folder0/folder1/folder2/photo.jpg" + val result2 = FileUtils.getFileNameFromPath(validPath2) + Assert.assertEquals("Got $result2 instead of $expectedResult", expectedResult, result2) + } + + @Test + fun `get file name from path with wrong path separator should return null`() { + val invalidPath = "\\folder\\photo.jpg" + val result = FileUtils.getFileNameFromPath(invalidPath) + Assert.assertNull("Got $result instead of null", result) + } + + @Test + @Ignore("Not ready yet") + fun `getMimeType for music`() { + val file = File("dummy.mp3") + println(file.toURI().toString()) + val result = FileUtils.getMimeType(file) + println(result) + } +} \ No newline at end of file -- GitLab From 7ba94ca21798f3a81cfc4c0ca8431a468a33d1ac Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Thu, 13 Jul 2023 11:29:32 +0200 Subject: [PATCH 09/10] add test cases and resources for 'FileUtils.getMimeType' --- .../foundation/e/drive/utils/FileUtils.kt | 20 +++++----- .../foundation/e/drive/utils/FileUtilsTest.kt | 36 ++++++++++++++---- files-for-test/picture.jpeg | Bin 0 -> 66410 bytes 3 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 files-for-test/picture.jpeg diff --git a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt index 109eb482..137a3caf 100644 --- a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt +++ b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt @@ -7,12 +7,11 @@ */ package foundation.e.drive.utils -import android.net.Uri -import android.webkit.MimeTypeMap import com.owncloud.android.lib.resources.files.FileUtils import timber.log.Timber import java.io.File import java.io.IOException +import java.net.URLConnection object FileUtils { /** @@ -49,18 +48,19 @@ object FileUtils { } /** - * Get mimetype of file from the file itself + * Guess mimetype of the file from file extension + * + * URLConnection method throw NPE if no extension or unknown extension (e.g: .tmo) * * @param file the file for whom we want Mime type - * @return String containing mimeType of the file + * @return String containing mimeType of the file for known type default value for others */ @JvmStatic fun getMimeType(file: File): String { - val fileExtension: String = MimeTypeMap.getFileExtensionFromUrl(file.toURI().toString()) - ?: return "*/*" - - return MimeTypeMap.getSingleton() - .getMimeTypeFromExtension(fileExtension.lowercase()) - ?: return "*/*" + try { + return URLConnection.guessContentTypeFromName(file.name) + } catch(e: java.lang.NullPointerException) { + return "*/*" + } } } \ No newline at end of file diff --git a/app/src/test/java/foundation/e/drive/utils/FileUtilsTest.kt b/app/src/test/java/foundation/e/drive/utils/FileUtilsTest.kt index f0c1d0d9..174fc2c2 100644 --- a/app/src/test/java/foundation/e/drive/utils/FileUtilsTest.kt +++ b/app/src/test/java/foundation/e/drive/utils/FileUtilsTest.kt @@ -1,8 +1,13 @@ +/* + * Copyright © MURENA SAS 2023. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ package foundation.e.drive.utils -import android.net.Uri import org.junit.Assert -import org.junit.Ignore import org.junit.Test import java.io.File @@ -35,11 +40,28 @@ internal class FileUtilsTest { } @Test - @Ignore("Not ready yet") - fun `getMimeType for music`() { - val file = File("dummy.mp3") - println(file.toURI().toString()) + fun `getMimeType from file with unusual extension `() { + val expected = "*/*" + val file = File("dummy.tmo") val result = FileUtils.getMimeType(file) - println(result) + + Assert.assertEquals("Expected $expected but got $result", expected, result) + } + + @Test + fun `getMimeType from file without extension `() { + val expected = "*/*" + val file = File("dummy") + val result = FileUtils.getMimeType(file) + + Assert.assertEquals("Expected $expected but got $result", expected, result) + } + + @Test + fun `getMimeType for picture file`() { + val expected = "image/jpeg" + val file = File("../files-for-test/picture.jpeg") + val result = FileUtils.getMimeType(file) + Assert.assertEquals("Expected $expected but got $result", expected, result) } } \ No newline at end of file diff --git a/files-for-test/picture.jpeg b/files-for-test/picture.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d663a2e5aa0c4fe68de1103bc57658dbd849e2b7 GIT binary patch literal 66410 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 Date: Thu, 13 Jul 2023 17:51:28 +0200 Subject: [PATCH 10/10] fix getFileNameFromPath to also handle folder ending with '/' --- app/src/main/java/foundation/e/drive/utils/FileUtils.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt index 137a3caf..77185ebd 100644 --- a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt +++ b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt @@ -8,6 +8,7 @@ package foundation.e.drive.utils import com.owncloud.android.lib.resources.files.FileUtils +import com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR import timber.log.Timber import java.io.File import java.io.IOException @@ -16,12 +17,14 @@ import java.net.URLConnection object FileUtils { /** * Return name of a file from its access path + * Also work with folder path. If last char is PATH separator, then the last occurence is removed. * - * @param path File name will be extracted from this path. Do not provide directory path + * @param rawPath File name will be extracted from this path. Do not provide directory path * @return String, the last part after separator of path or null if invalid path has been provided */ @JvmStatic - fun getFileNameFromPath(path: String): String? { + fun getFileNameFromPath(rawPath: String): String? { + val path = rawPath.removeSuffix(PATH_SEPARATOR) val lastIndexOfPathSeparator = path.lastIndexOf(FileUtils.PATH_SEPARATOR) if (lastIndexOfPathSeparator == -1) return null return path.substring(lastIndexOfPathSeparator + 1) -- GitLab