performance: move local file#17082
Conversation
There was a problem hiding this comment.
Pull request overview
Refactors FileDataStorageManager.moveLocalFile(...) to a Kotlin extension-based implementation intended to improve performance/clarity, and adds unit tests around DB updates, filesystem moves, and media scan behavior when moving files/folders.
Changes:
- Replaced the inlined Java
moveLocalFileimplementation with a delegation toFileDataStorageManagerExtensions.moveFiles. - Added a Room DAO bulk update method (
FileDao.updateAll) and moved descendant path/storagePath update logic into DAO-backed Kotlin code. - Added a new unit test suite covering guard conditions, entity updates, hierarchy updates, filesystem rename behavior, and media scan triggering.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java | Delegates moveLocalFile to the Kotlin extension and exposes TAG for shared logging. |
| app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt | Introduces moveFiles, filesystem move helper, and DB update logic for moved descendants + media scan handling. |
| app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt | Adds updateAll(List<FileEntity>) for bulk updates used by the move logic. |
| app/src/test/java/com/owncloud/android/datamodel/MoveFilesTestBase.kt | Shared mocking/setup utilities for the new move-local-file test suite. |
| app/src/test/java/com/owncloud/android/datamodel/MoveFilesGuardTest.kt | Tests early-return guard behavior (null/nonexistent/root/invalid parent, etc.). |
| app/src/test/java/com/owncloud/android/datamodel/MoveFileEntitiesUpdateTest.kt | Tests DB entity field updates (path/pathDecrypted/storagePath/parent) for moves. |
| app/src/test/java/com/owncloud/android/datamodel/MoveFilesFilesystemAndMediaTest.kt | Tests filesystem rename and media scan behavior for moved media vs non-media files. |
| app/src/test/java/com/owncloud/android/datamodel/MoveFilesHierarchyTest.kt | Tests descendant updates (paths/parents/storagePath) when moving folders with hierarchies. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fun testMoveLocalFileWhenFileDoesNotExistShouldReturnEarlyWithoutInteractingWithDatabase() { | ||
| val file = OCFile(OLD_PATH).apply { fileId = 0 } | ||
|
|
||
| manager.moveLocalFile(file, TARGET_PATH, TARGET_PARENT_PATH) | ||
|
|
||
| verify(exactly = 0) { mockFileDao.getFolderWithDescendants(any(), any()) } | ||
| verify(exactly = 0) { mockFileDao.updateAll(any()) } | ||
| } |
| val originalMediaPaths = | ||
| fileDao.moveFilesInDb(oldPath, targetPath, defaultSavePath, targetParent.fileId, accountName) | ||
|
|
||
| val moved = moveLocalFiles(accountName, ocFile, defaultSavePath, targetPath) | ||
| if (!moved) return | ||
|
|
| Log_OC.e(FileDataStorageManager.TAG, "moveLocalFile: file does not exist, skipping") | ||
| return | ||
| } | ||
|
|
||
| if (OCFile.ROOT_PATH == ocFile.fileName) { | ||
| Log_OC.e(FileDataStorageManager.TAG, "moveLocalFile: cannot move root path") | ||
| return | ||
| } | ||
|
|
||
| if (ocFile.remotePath == targetPath) { | ||
| Log_OC.e(FileDataStorageManager.TAG, "moveLocalFile: source and target paths are identical, skipping") | ||
| return | ||
| } | ||
|
|
||
| val targetParent = getFileByPath(targetParentPath) | ||
| if (targetParent == null) { | ||
| Log_OC.e(FileDataStorageManager.TAG, "moveLocalFile: target parent folder not found: $targetParentPath") | ||
| return | ||
| } | ||
|
|
||
| if (!targetParent.isFolder) { | ||
| Log_OC.e(FileDataStorageManager.TAG, "moveLocalFile: target parent is not a folder: $targetParentPath") |
|
APK file: https://github.com/nextcloud/android/actions/runs/26279321751/artifacts/7157181307 |
|
blue-Light-Screenshot test failed, but no output was generated. Maybe a preliminary stage failed. |
| @Suppress("ReturnCount") | ||
| private fun moveLocalFiles(accountName: String, ocFile: OCFile, defaultSavePath: String, targetPath: String): Boolean { | ||
| val localFile = File(FileStorageUtils.getDefaultSavePathFor(accountName, ocFile)) | ||
| if (!localFile.exists()) { |
|
|
||
| val targetFile = File(defaultSavePath + targetPath) | ||
| val targetFolder = targetFile.getParentFile() | ||
| if (targetFolder != null && !targetFolder.exists() && !targetFolder.mkdirs()) { |
|
|
||
| val targetFile = File(defaultSavePath + targetPath) | ||
| val targetFolder = targetFile.getParentFile() | ||
| if (targetFolder != null && !targetFolder.exists() && !targetFolder.mkdirs()) { |
| ) | ||
| } | ||
|
|
||
| if (!localFile.renameTo(targetFile)) { |
| ) | ||
| } | ||
|
|
||
| if (!localFile.renameTo(targetFile)) { |
Issue
Renaming folder too slow
Changes
How to test?
Demo
Screen.Recording.2026-05-22.at.12.11.42.mp4