From 2734df5157b410897d8116a9f80ed7e2146dde33 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Fri, 11 Sep 2026 19:00:28 +0200 Subject: [PATCH 1/9] feature/create-folder-during-upload: use new auto folder creation feature Signed-off-by: daniele-verducci --- .../android/operations/UploadFileOperation.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index c71fe7d504a6..26941bffcaa0 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -55,6 +55,7 @@ import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation; import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation; import com.owncloud.android.lib.resources.files.model.RemoteFile; +import com.owncloud.android.lib.resources.status.NextcloudVersion; import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.operations.common.SyncOperation; import com.owncloud.android.operations.e2e.E2EClientData; @@ -477,12 +478,15 @@ protected RemoteOperationResult run(OwnCloudClient client) { final boolean isResumingEncryptedUpload = (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()); if (!isResumingEncryptedUpload && (parent == null || mRemoteFolderToBeCreated)) { Log_OC.d(TAG, "verifying remote parent folder exists: " + remoteParentPath); - final var result = grantFolderExistence(remoteParentPath, client); - if (!result.isSuccess()) { - Log_OC.e(TAG, "grantFolderExistence failed for: " + remoteParentPath + ", code: " + - result.getCode() + ", message: " + result.getMessage()); - return result; + if (!getCapabilities().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_32)) { + // No automatic folder creation before Nextcloud 32, create them + final var result = grantFolderExistence(remoteParentPath, client); + if (!result.isSuccess()) { + Log_OC.e(TAG, "grantFolderExistence failed for: " + remoteParentPath + ", code: " + + result.getCode() + ", message: " + result.getMessage()); + return result; + } } parent = getStorageManager().getFileByPath(remoteParentPath); From 0dcc1b4c3a5371b77e22d58ca547b4c6a395bde2 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Mon, 14 Sep 2026 15:55:05 +0200 Subject: [PATCH 2/9] Updated android-library Signed-off-by: daniele-verducci --- gradle/verification-metadata.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index e7a57c52a452..22c99f8912b2 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -21859,10 +21859,17 @@ +<<<<<<< HEAD +======= + + + + +>>>>>>> bb17089ca0 (Updated android-library) From d92016b48f0a29d4f19a0e54d281edf50dec4ea9 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Mon, 14 Sep 2026 17:49:39 +0200 Subject: [PATCH 3/9] Working new auto folder creation feature on uploads Signed-off-by: daniele-verducci --- .../operations/UploadFileOperation.java | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index 26941bffcaa0..f7a0793ff909 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -475,40 +475,42 @@ protected RemoteOperationResult run(OwnCloudClient client) { Log_OC.d(TAG, "parent lookup for path: " + remoteParentPath + " → " + (parent == null ? "not found in DB" : "found, id=" + parent.getFileId())); - final boolean isResumingEncryptedUpload = (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()); - if (!isResumingEncryptedUpload && (parent == null || mRemoteFolderToBeCreated)) { - Log_OC.d(TAG, "verifying remote parent folder exists: " + remoteParentPath); - if (!getCapabilities().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_32)) { - // No automatic folder creation before Nextcloud 32, create them + // No automatic folder creation before Nextcloud 32 + if (!getCapabilities().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_32)) { + final boolean isResumingEncryptedUpload = (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()); + if (!isResumingEncryptedUpload && (parent == null || mRemoteFolderToBeCreated)) { + Log_OC.d(TAG, "verifying remote parent folder exists: " + remoteParentPath); final var result = grantFolderExistence(remoteParentPath, client); + if (!result.isSuccess()) { Log_OC.e(TAG, "grantFolderExistence failed for: " + remoteParentPath + ", code: " + result.getCode() + ", message: " + result.getMessage()); return result; } + + parent = getStorageManager().getFileByPath(remoteParentPath); + if (parent == null) { + Log_OC.e(TAG, "parent still null after grantFolderExistence: " + remoteParentPath); + return new RemoteOperationResult<>(ResultCode.UNKNOWN_ERROR); + } + + Log_OC.d(TAG, "remote parent folder confirmed: " + remoteParentPath + ", id=" + parent.getFileId()); } - parent = getStorageManager().getFileByPath(remoteParentPath); if (parent == null) { - Log_OC.e(TAG, "parent still null after grantFolderExistence: " + remoteParentPath); - return new RemoteOperationResult<>(ResultCode.UNKNOWN_ERROR); + Log_OC.e(TAG, "parent is null, cannot proceed: " + remoteParentPath + "," + " unlock token: " + mFolderUnlockToken); + return new RemoteOperationResult<>(false, "Parent folder not found", HttpStatus.SC_NOT_FOUND); } - - Log_OC.d(TAG, "remote parent folder confirmed: " + remoteParentPath + ", id=" + parent.getFileId()); } - if (parent == null) { - Log_OC.e(TAG, "parent is null, cannot proceed: " + remoteParentPath + "," + " unlock token: " + mFolderUnlockToken); - return new RemoteOperationResult<>(false, "Parent folder not found", HttpStatus.SC_NOT_FOUND); - } - - // - resume of encrypted upload, then parent file exists already as unlock is only for direct parent - mFile.setParentId(parent.getFileId()); - // check if any parent is encrypted encryptedAncestor = FileStorageUtils.checkEncryptionStatus(parent, getStorageManager()); mFile.setEncrypted(encryptedAncestor); + if (encryptedAncestor && parent != null) { + // - resume of encrypted upload, then parent file exists already as unlock is only for direct parent + mFile.setParentId(parent.getFileId()); + } if (encryptedAncestor) { Log_OC.d(TAG, "⬆️🔗" + "encrypted upload"); From 47fbc334d2edfba5002e42ea10fe7aaa0bfbdfa8 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Tue, 15 Sep 2026 11:27:32 +0200 Subject: [PATCH 4/9] Apply suggestion from @alperozturk96 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alper Öztürk <67455295+alperozturk96@users.noreply.github.com> Signed-off-by: daniele-verducci --- .../com/owncloud/android/operations/UploadFileOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index f7a0793ff909..9302bd340455 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -476,7 +476,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { (parent == null ? "not found in DB" : "found, id=" + parent.getFileId())); - // No automatic folder creation before Nextcloud 32 + // Create folders only for Nextcloud < 32; 32+ handles this automatically. if (!getCapabilities().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_32)) { final boolean isResumingEncryptedUpload = (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()); if (!isResumingEncryptedUpload && (parent == null || mRemoteFolderToBeCreated)) { From 8924ac6bfce801e2add8b2280e2c2a890c47bbe5 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Tue, 15 Sep 2026 11:32:08 +0200 Subject: [PATCH 5/9] Use isOlderThan on capabilities for code readability Signed-off-by: daniele-verducci --- .../com/owncloud/android/operations/UploadFileOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index 9302bd340455..63f49f567720 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -477,7 +477,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { // Create folders only for Nextcloud < 32; 32+ handles this automatically. - if (!getCapabilities().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_32)) { + if (getCapabilities().getVersion().isOlderThan(NextcloudVersion.nextcloud_32)) { final boolean isResumingEncryptedUpload = (mFolderUnlockToken != null && !mFolderUnlockToken.isEmpty()); if (!isResumingEncryptedUpload && (parent == null || mRemoteFolderToBeCreated)) { Log_OC.d(TAG, "verifying remote parent folder exists: " + remoteParentPath); From 66e0c5d352c60d6864a2517fe4cca6f0d7c8260a Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Tue, 15 Sep 2026 11:41:31 +0200 Subject: [PATCH 6/9] Rebased Signed-off-by: daniele-verducci --- gradle/verification-metadata.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 22c99f8912b2..e7a57c52a452 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -21859,17 +21859,10 @@ -<<<<<<< HEAD -======= - - - - ->>>>>>> bb17089ca0 (Updated android-library) From e9a612af51feac8c28e4c42aca42f5fd4f46583d Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Wed, 16 Sep 2026 17:46:10 +0200 Subject: [PATCH 7/9] Moved dir tree creation to extensions (automatic java to kotlin conversion) Signed-off-by: daniele-verducci --- .../FileDataStorageManagerExtensions.kt | 30 +++++++++++++++++++ .../operations/CreateFolderOperation.java | 29 ++---------------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt index fab3fe145c54..3c22fb1dd0f3 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt @@ -17,6 +17,7 @@ import com.owncloud.android.lib.resources.files.model.RemoteFile import com.owncloud.android.lib.resources.shares.OCShare import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.utils.FileStorageUtils +import com.owncloud.android.utils.MimeType import com.owncloud.android.utils.MimeTypeUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -185,6 +186,35 @@ fun FileDataStorageManager.moveFiles(ocFile: OCFile?, targetPath: String, target } } +fun FileDataStorageManager.createDirectoryTree(remotePath: String, createdRemoteFolder: RemoteFile) { + if (getFileByPath(FileStorageUtils.getParentPath(remotePath)) == null) { + // When parent of remote path is not created + val subFolders = remotePath.split(OCFile.PATH_SEPARATOR.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + var composedRemotePath = OCFile.ROOT_PATH + + // For each ancestor folders create them recursively + for (subFolder in subFolders) { + if (!subFolder.isEmpty()) { + composedRemotePath = composedRemotePath + subFolder + OCFile.PATH_SEPARATOR + createDirectoryTree(composedRemotePath, createdRemoteFolder) + } + } + } else { + // Create directory on DB + val newDir = OCFile(remotePath) + newDir.setMimeType(MimeType.DIRECTORY) + val parentId: Long = getFileByPath(FileStorageUtils.getParentPath(remotePath)).getFileId() + newDir.setParentId(parentId) + newDir.setRemoteId(createdRemoteFolder.remoteId) + newDir.setModificationTimestamp(System.currentTimeMillis()) + newDir.setEncrypted(FileStorageUtils.checkEncryptionStatus(newDir, this)) + newDir.setPermissions(createdRemoteFolder.permissions) + saveFile(newDir) + + Log_OC.d(FileDataStorageManager.TAG, "createDirectoryTree: created " + remotePath + " in Database") + } +} + @Suppress("ReturnCount") private fun moveLocalFiles(accountName: String, ocFile: OCFile, defaultSavePath: String, targetPath: String): Boolean { val localFile = File(FileStorageUtils.getDefaultSavePathFor(accountName, ocFile)) diff --git a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java index 6c57e9965b23..d00963f31ba2 100644 --- a/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java @@ -18,6 +18,7 @@ import com.nextcloud.client.account.User; import com.nextcloud.utils.e2ee.E2ECounterHelper; import com.nextcloud.utils.e2ee.E2EVersionHelper; +import com.nextcloud.utils.extensions.FileDataStorageManagerExtensionsKt; import com.nextcloud.utils.extensions.OCFileExtensionsKt; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; @@ -552,33 +553,7 @@ private void onCreateRemoteFolderOperationFinish(RemoteOperationResult result) { * Save new directory in local database. */ private void saveFolderInDB() { - if (getStorageManager().getFileByPath(FileStorageUtils.getParentPath(remotePath)) == null) { - // When parent of remote path is not created - String[] subFolders = remotePath.split(PATH_SEPARATOR); - String composedRemotePath = ROOT_PATH; - - // For each ancestor folders create them recursively - for (String subFolder : subFolders) { - if (!subFolder.isEmpty()) { - composedRemotePath = composedRemotePath + subFolder + PATH_SEPARATOR; - remotePath = composedRemotePath; - saveFolderInDB(); - } - } - } else { - // Create directory on DB - OCFile newDir = new OCFile(remotePath); - newDir.setMimeType(MimeType.DIRECTORY); - long parentId = getStorageManager().getFileByPath(FileStorageUtils.getParentPath(remotePath)).getFileId(); - newDir.setParentId(parentId); - newDir.setRemoteId(createdRemoteFolder.getRemoteId()); - newDir.setModificationTimestamp(System.currentTimeMillis()); - newDir.setEncrypted(FileStorageUtils.checkEncryptionStatus(newDir, getStorageManager())); - newDir.setPermissions(createdRemoteFolder.getPermissions()); - getStorageManager().saveFile(newDir); - - Log_OC.d(TAG, "Create directory " + remotePath + " in Database"); - } + FileDataStorageManagerExtensionsKt.createDirectoryTree(getStorageManager(), remotePath, createdRemoteFolder); } public String getRemotePath() { From fdf5b4b30b0aa364d6bc12b7508a4626df4e2377 Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Wed, 16 Sep 2026 17:51:47 +0200 Subject: [PATCH 8/9] Refactored converted kotlin to a more idiomatic one Signed-off-by: daniele-verducci --- .../FileDataStorageManagerExtensions.kt | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt index 3c22fb1dd0f3..da0aab36182c 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt @@ -187,31 +187,32 @@ fun FileDataStorageManager.moveFiles(ocFile: OCFile?, targetPath: String, target } fun FileDataStorageManager.createDirectoryTree(remotePath: String, createdRemoteFolder: RemoteFile) { - if (getFileByPath(FileStorageUtils.getParentPath(remotePath)) == null) { + if (getFileByEncryptedRemotePath(FileStorageUtils.getParentPath(remotePath)) == null) { // When parent of remote path is not created val subFolders = remotePath.split(OCFile.PATH_SEPARATOR.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() var composedRemotePath = OCFile.ROOT_PATH // For each ancestor folders create them recursively for (subFolder in subFolders) { - if (!subFolder.isEmpty()) { + if (subFolder.isNotEmpty()) { composedRemotePath = composedRemotePath + subFolder + OCFile.PATH_SEPARATOR createDirectoryTree(composedRemotePath, createdRemoteFolder) } } } else { // Create directory on DB - val newDir = OCFile(remotePath) - newDir.setMimeType(MimeType.DIRECTORY) - val parentId: Long = getFileByPath(FileStorageUtils.getParentPath(remotePath)).getFileId() - newDir.setParentId(parentId) - newDir.setRemoteId(createdRemoteFolder.remoteId) - newDir.setModificationTimestamp(System.currentTimeMillis()) - newDir.setEncrypted(FileStorageUtils.checkEncryptionStatus(newDir, this)) - newDir.setPermissions(createdRemoteFolder.permissions) - saveFile(newDir) - - Log_OC.d(FileDataStorageManager.TAG, "createDirectoryTree: created " + remotePath + " in Database") + with(OCFile(remotePath)) { + mimeType = MimeType.DIRECTORY + val parentId: Long = getFileByEncryptedRemotePath(FileStorageUtils.getParentPath(remotePath)).getFileId() + setParentId(parentId) + remoteId = createdRemoteFolder.remoteId + modificationTimestamp = System.currentTimeMillis() + isEncrypted = FileStorageUtils.checkEncryptionStatus(this, this@createDirectoryTree) + permissions = createdRemoteFolder.permissions + saveFile(this) + } + + Log_OC.d(FileDataStorageManager.TAG, "createDirectoryTree: created $remotePath in Database") } } From 3d0053b87772fecab670957dd9f7c22ed8d165ed Mon Sep 17 00:00:00 2001 From: daniele-verducci Date: Wed, 16 Sep 2026 18:10:55 +0200 Subject: [PATCH 9/9] Create local folders metadata to reflect server's auto folder creation Signed-off-by: daniele-verducci --- .../extensions/FileDataStorageManagerExtensions.kt | 6 +++--- .../android/operations/UploadFileOperation.java | 13 +++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt index da0aab36182c..437b3c32eda7 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt @@ -186,7 +186,7 @@ fun FileDataStorageManager.moveFiles(ocFile: OCFile?, targetPath: String, target } } -fun FileDataStorageManager.createDirectoryTree(remotePath: String, createdRemoteFolder: RemoteFile) { +fun FileDataStorageManager.createDirectoryTree(remotePath: String, createdRemoteFolder: RemoteFile?) { if (getFileByEncryptedRemotePath(FileStorageUtils.getParentPath(remotePath)) == null) { // When parent of remote path is not created val subFolders = remotePath.split(OCFile.PATH_SEPARATOR.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() @@ -205,10 +205,10 @@ fun FileDataStorageManager.createDirectoryTree(remotePath: String, createdRemote mimeType = MimeType.DIRECTORY val parentId: Long = getFileByEncryptedRemotePath(FileStorageUtils.getParentPath(remotePath)).getFileId() setParentId(parentId) - remoteId = createdRemoteFolder.remoteId + remoteId = createdRemoteFolder?.remoteId modificationTimestamp = System.currentTimeMillis() isEncrypted = FileStorageUtils.checkEncryptionStatus(this, this@createDirectoryTree) - permissions = createdRemoteFolder.permissions + permissions = createdRemoteFolder?.permissions saveFile(this) } diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index 63f49f567720..0fc9dc253044 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -27,6 +27,7 @@ import com.nextcloud.utils.autoRename.AutoRename; import com.nextcloud.utils.e2ee.E2ECounterHelper; import com.nextcloud.utils.e2ee.E2EVersionHelper; +import com.nextcloud.utils.extensions.FileDataStorageManagerExtensionsKt; import com.nextcloud.utils.extensions.RemoteOperationResultExtensionsKt; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; @@ -512,13 +513,21 @@ protected RemoteOperationResult run(OwnCloudClient client) { mFile.setParentId(parent.getFileId()); } + RemoteOperationResult result; if (encryptedAncestor) { Log_OC.d(TAG, "⬆️🔗" + "encrypted upload"); - return encryptedUpload(client, parent); + result = encryptedUpload(client, parent); } else { Log_OC.d(TAG, "⬆️" + "normal upload"); - return normalUpload(client); + result = normalUpload(client); } + + if (result.isSuccess() && getCapabilities().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_32)) { + // Add the folders that the server creates automatically (from Nextcloud 32) to local db + FileDataStorageManagerExtensionsKt.createDirectoryTree(getStorageManager(), remoteParentPath, null); + } + + return result; } // region E2E Upload