Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1575,7 +1575,13 @@ class FileDisplayActivity :

private fun handleRemovedFolder(syncFolderRemotePath: String?) {
DisplayUtils.showSnackMessage(this, R.string.sync_current_folder_was_removed, syncFolderRemotePath)
browseToRoot()
fileListFragment?.let {
it.parentFolderFinder.getParentOnFirstParentRemoved(syncFolderRemotePath, storageManager)?.let { target ->
it.listDirectory(target, MainApp.isOnlyOnDevice())
updateActionBarTitleAndHomeButton(target)
file = target
}
}
}

private fun updateFileList(
Expand Down Expand Up @@ -1899,13 +1905,13 @@ class FileDisplayActivity :
// endregion

fun browseToRoot() {
val listOfFiles = this.listOfFilesFragment
if (listOfFiles != null) { // should never be null, indeed
listOfFilesFragment?.let {
val root = storageManager.getFileByPath(OCFile.ROOT_PATH)
listOfFiles.resetSearchAttributes()
file = listOfFiles.currentFile
it.resetSearchAttributes()
file = it.currentFile
startSyncFolderOperation(root, false)
}

binding.fabMain.setImageResource(R.drawable.ic_plus)
resetScrollingAndUpdateActionBar()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@
import com.owncloud.android.lib.resources.e2ee.ToggleEncryptionRemoteOperation;
import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
import com.owncloud.android.lib.resources.files.ToggleFavoriteRemoteOperation;
import com.owncloud.android.lib.resources.files.model.RemoteFile;
import com.owncloud.android.lib.resources.status.E2EVersion;
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.lib.resources.status.Type;
Expand All @@ -106,6 +105,7 @@
import com.owncloud.android.ui.events.FavoriteEvent;
import com.owncloud.android.ui.events.FileLockEvent;
import com.owncloud.android.ui.events.SearchEvent;
import com.owncloud.android.ui.fragment.helper.ParentFolderFinder;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface;
import com.owncloud.android.ui.preview.PreviewImageFragment;
Expand All @@ -124,7 +124,6 @@
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
Expand Down Expand Up @@ -228,6 +227,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
protected String mLimitToMimeType;
private FloatingActionButton mFabMain;
public static boolean isMultipleFileSelectedForCopyOrMove = false;
public final ParentFolderFinder parentFolderFinder = new ParentFolderFinder();

private static final Intent scanIntentExternalApp = new Intent("org.fairscan.app.action.SCAN_TO_PDF");

Expand Down Expand Up @@ -1019,7 +1019,6 @@ private void updateSortAndGridMenuItems() {
}
}


/**
* Call this, when the user presses the up button.
* <p>
Expand All @@ -1029,62 +1028,27 @@ private void updateSortAndGridMenuItems() {
* return Count of folder levels browsed up.
*/
public int onBrowseUp() {
if (mFile == null) {
if (mFile == null || mFile.isRootDirectory()) {
return 0;
}

final var result = parentFolderFinder.getParent(mFile, mContainerActivity.getStorageManager());
OCFile target = result.getSecond();

if (target == null) {
Log_OC.e(TAG, "onBrowseUp: could not resolve parent, staying put");
return 0;
}

Pair<Integer, OCFile> result = getPreviousFile();
mFile = result.second;
mFile = target;
setFileDepth(mFile);

// since on browse down sets it to the false, browse up should set back to true if current search type is not NO_SEARCH
if (mFile.isRootDirectory() && currentSearchType != NO_SEARCH) {
searchFragment = true;
}

updateFileList();
return result.first;
}

private Pair<Integer, OCFile> getPreviousFile() {
if (mFile == null) {
return new Pair<>(0, null);
}

FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
int moveCount = 0;
String parentPath;
OCFile parentDir;

if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
parentPath = new File(mFile.getRemotePath()).getParent();
parentPath = ensureTrailingSeparator(parentPath);
parentDir = storageManager.getFileByPath(parentPath);
moveCount++;
} else {
parentDir = storageManager.getFileByPath(ROOT_PATH);
parentPath = ROOT_PATH;
}

// Keep going up until we find a valid folder
while (parentDir == null && !ROOT_PATH.equals(parentPath)) {
parentPath = new File(parentPath).getParent();
if (parentPath == null) {
parentPath = ROOT_PATH; // fallback to root
}
parentPath = ensureTrailingSeparator(parentPath);
parentDir = storageManager.getFileByPath(parentPath);
moveCount++;
}

return new Pair<>(moveCount, parentDir);
}

private String ensureTrailingSeparator(String path) {
if (path == null) {
return ROOT_PATH;
}
return path.endsWith(OCFile.PATH_SEPARATOR) ? path : path + OCFile.PATH_SEPARATOR;
return result.getFirst();
}

private void updateFileList() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.owncloud.android.ui.fragment.helper

import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.utils.Log_OC

@Suppress("ReturnCount")
class ParentFolderFinder {
companion object {
private const val TAG = "ParentFolderFinder"
}

/**
* User tries to move up but parent folder was deleted thus parent of parent
* will be used as destination else ROOT directory
*/
fun getParentOnFirstParentRemoved(path: String?, storageManager: FileDataStorageManager?): OCFile? {
if (storageManager == null) {
Log_OC.e(TAG, "StorageManager is null")
return null
}

if (path.isNullOrEmpty() || path == OCFile.ROOT_PATH) {
Log_OC.w(TAG, "Path is null, empty, or already at root. Falling back to ROOT.")
return storageManager.getFileByEncryptedRemotePath(OCFile.ROOT_PATH)
}

return walkUpByPath(path, storageManager).second
}

fun getParent(file: OCFile?, storageManager: FileDataStorageManager?): Pair<Int, OCFile?> {
if (file == null || file.isRootDirectory) {
Log_OC.e(TAG, "File is null or already at root")
return 0 to file
}

if (storageManager == null) {
Log_OC.e(TAG, "StorageManager is null")
return 0 to file
}

// id based lookup
val parentId = file.parentId
if (parentId > FileDataStorageManager.ROOT_PARENT_ID && parentId != file.fileId) {
storageManager.getFileById(parentId)?.let { parentById ->
if (parentById.isFolder) {
return 1 to parentById
}
}

Log_OC.w(TAG, "ID lookup missed for parentId=$parentId, falling back to path walk")
}

// path-based walk up
return walkUpByPath(file.remotePath, storageManager)
}

/**
* Walks the remote path upward one segment at a time until a valid folder
* is found in the DB, or we reach root.
*/
private fun walkUpByPath(initialPath: String?, storageManager: FileDataStorageManager): Pair<Int, OCFile?> {
var path = initialPath
var moveCount = 0

while (!path.isNullOrEmpty() && path != OCFile.ROOT_PATH) {
val parentPath = resolveParentPath(path)
moveCount++

if (parentPath == null) {
Log_OC.w(TAG, "Failed to resolve parent path for $path, fallback to root")
break
}

// Check if this resolved parent exists in DB
storageManager.getFileByEncryptedRemotePath(parentPath)?.let { candidate ->
if (candidate.isFolder) {
return moveCount to candidate
}
}

if (parentPath == OCFile.ROOT_PATH) {
Log_OC.w(TAG, "Root path reached but not found in DB loop, forcing fallback")
break
}

// Move up one more level for the next iteration
path = parentPath
}

// fallback to root
val root = storageManager.getFileByEncryptedRemotePath(OCFile.ROOT_PATH)
return moveCount to root
}

/**
* Returns the parent path of `path` with a guaranteed trailing "/",
* or `null` if `path` is already root or invalid.
*
*
* Correctly handles paths with or without a trailing separator, so both
* "/Foo/Bar" and "/Foo/Bar/" return "/Foo/".
*/
private fun resolveParentPath(path: String?): String? {
if (path.isNullOrEmpty() || path == OCFile.ROOT_PATH) {
return null
}

// Strip trailing "/" before computing parent
val normalized = if (path.endsWith(OCFile.PATH_SEPARATOR)) {
path.substring(0, path.length - 1)
} else {
path
}

val lastSep = normalized.lastIndexOf(OCFile.PATH_SEPARATOR)
if (lastSep <= 0) {
Log_OC.w(TAG, "No separator found, or only one at the start")
return OCFile.ROOT_PATH
}

// e.g. "/Foo/Bar" → "/Foo/"
return normalized.substring(0, lastSep + 1)
}
}
Loading
Loading