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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 R1SdEwl`0ox$dCVj698b7K0g2e literal 0 HcmV?d00001 -- GitLab From e1c1d5495cd189328684226bb8b5e19d1374bdd1 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer 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