Skip to content

Commit cc7c385

Browse files
authored
fix: resolve nested path creation from parent listings (#1935)
1 parent d79a906 commit cc7c385

3 files changed

Lines changed: 106 additions & 100 deletions

File tree

src/lib/openFolder.js

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -664,25 +664,40 @@ function execOperation(type, action, url, $target, name) {
664664
newName = helpers.fixFilename(newName);
665665
if (!newName) return;
666666
startLoading();
667-
let newUrl;
667+
try {
668+
const isNestedPath = newName.split("/").filter(Boolean).length > 1;
669+
let newUrl;
668670

669-
if (action === "new file") {
670-
newUrl = await helpers.createFileStructure(url, newName);
671-
} else {
672-
newUrl = await helpers.createFileStructure(url, newName, false);
673-
}
674-
if (!newUrl) return;
675-
newName = Url.basename(newUrl.uri);
676-
if ($target.unclasped) {
677-
if (newUrl.type === "file") {
678-
appendTile($target, createFileTile(newName, newUrl.uri));
679-
} else if (newUrl.type === "folder") {
680-
appendList($target, createFolderTile(newName, newUrl.uri));
671+
if (action === "new file") {
672+
newUrl = await helpers.createFileStructure(url, newName);
673+
} else {
674+
newUrl = await helpers.createFileStructure(url, newName, false);
681675
}
682-
}
676+
if (!newUrl.created) return;
683677

684-
FileList.append(url, newUrl.uri);
685-
toast(strings.success);
678+
if (isNestedPath) {
679+
openFolder.find(url)?.reload();
680+
await FileList.refresh();
681+
toast(strings.success);
682+
return;
683+
}
684+
685+
newName = Url.basename(newUrl.uri);
686+
if ($target.unclasped) {
687+
if (newUrl.type === "file") {
688+
appendTile($target, createFileTile(newName, newUrl.uri));
689+
} else if (newUrl.type === "folder") {
690+
appendList($target, createFolderTile(newName, newUrl.uri));
691+
}
692+
}
693+
694+
FileList.append(url, newUrl.uri);
695+
toast(strings.success);
696+
} catch (error) {
697+
helpers.error(error);
698+
} finally {
699+
stopLoading();
700+
}
686701
}
687702

688703
async function paste() {

src/pages/fileBrowser/fileBrowser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1270,7 +1270,7 @@ function FileBrowserInclude(mode, info, doesOpenLast = true) {
12701270
if (arg === "file") {
12711271
newUrl = await helpers.createFileStructure(url, entryName);
12721272
}
1273-
if (!newUrl) return;
1273+
if (!newUrl.created) return;
12741274
return newUrl.uri;
12751275
}
12761276

src/utils/helpers.js

Lines changed: 74 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -418,73 +418,28 @@ export default {
418418
const terminalBasePath = isAcodeTerminalPublicSafUri
419419
? decodeURIComponent(treeSegment.split("::")[0] || "")
420420
: "";
421-
422-
if (isExternalStorageUri) {
423-
baseFolder = decodeURIComponent(currentUri.split("%3A")[1].split("/")[0]);
424-
} else if (
425-
!(isExternalStorageUri || isTermuxUri || isAcodeTerminalPublicSafUri)
426-
) {
427-
// Handle nested paths for regular file:// URIs
428-
const pathParts = pathString.split("/").filter(Boolean);
429-
let currentPath = uri;
430-
let firstCreatedPath = null;
431-
let firstCreatedType = null;
432-
433-
for (let i = 0; i < pathParts.length; i++) {
434-
const isLastPart = i === pathParts.length - 1;
435-
const partName = pathParts[i];
436-
const newPath = Url.join(currentPath, partName);
437-
438-
if (isLastPart && isFile) {
439-
// Create file if it's the last part and we're creating a file
440-
if (!(await fsOperation(newPath).exists())) {
441-
await fsOperation(currentPath).createFile(partName);
442-
if (firstCreatedPath === null) {
443-
firstCreatedPath = newPath;
444-
firstCreatedType = "file";
445-
}
446-
}
447-
} else {
448-
// Create directory for intermediate parts or when creating a folder
449-
if (!(await fsOperation(newPath).exists())) {
450-
await fsOperation(currentPath).createDirectory(partName);
451-
if (firstCreatedPath === null) {
452-
firstCreatedPath = newPath;
453-
firstCreatedType = "folder";
454-
}
455-
}
456-
}
457-
currentPath = newPath;
421+
const getTargetUri = (baseUri, name, index) => {
422+
if (
423+
!(isExternalStorageUri || isTermuxUri || isAcodeTerminalPublicSafUri)
424+
) {
425+
return Url.join(baseUri, name);
458426
}
459427

460-
return {
461-
uri: firstCreatedPath || Url.join(uri, pathParts[0]),
462-
type:
463-
firstCreatedType ||
464-
(isFile && pathParts.length === 1 ? "file" : "folder"),
465-
};
466-
}
467-
468-
for (let i = 0; i < parts.length; i++) {
469-
const isLastElement = i === parts.length - 1;
470-
const name = parts[i];
471-
let fullUri = currentUri;
472-
473-
// Adjust URI for special cases
428+
let fullUri = baseUri;
474429
if (isExternalStorageUri) {
475-
if (!isSpecialCase && i === 0) {
430+
if (!isSpecialCase && index === 0) {
476431
fullUri += `::primary:${baseFolder}/${name}`;
477432
} else {
478433
fullUri += `/${name}`;
479434
}
480435
} else if (isTermuxUri) {
481-
if (!isSpecialCase && i === 0) {
436+
if (!isSpecialCase && index === 0) {
482437
fullUri += `::/data/data/com.termux/files/home/${name}`;
483438
} else {
484439
fullUri += `/${name}`;
485440
}
486441
} else if (isAcodeTerminalPublicSafUri) {
487-
if (!isSpecialCase && i === 0) {
442+
if (!isSpecialCase && index === 0) {
488443
const sanitizedBase = terminalBasePath.endsWith("/")
489444
? `${terminalBasePath}${name}`
490445
: `${terminalBasePath}/${name}`;
@@ -493,41 +448,77 @@ export default {
493448
fullUri += `/${name}`;
494449
}
495450
}
496-
497-
if (isLastElement && isFile) {
498-
// Create file if it's the last element and isFile is true
499-
if (!(await fsOperation(fullUri).exists())) {
500-
await fsOperation(currentUri).createFile(name);
501-
} else {
502-
return;
503-
}
504-
} else {
505-
// Create directory
506-
if (!(await fsOperation(fullUri).exists())) {
507-
await fsOperation(currentUri).createDirectory(name);
508-
} else {
509-
return;
451+
return fullUri;
452+
};
453+
const getExpectedType = (isLastPart) =>
454+
isLastPart && isFile ? "file" : "folder";
455+
const ensureEntry = async (baseUri, targetUri, name, expectedType) => {
456+
const entries = await fsOperation(baseUri).lsDir();
457+
const existingEntry = entries.find((entry) => entry.name === name);
458+
459+
if (existingEntry) {
460+
const actualType = existingEntry.isDirectory ? "folder" : "file";
461+
if (actualType !== expectedType) {
462+
throw new Error(
463+
`${name} already exists as a ${actualType}, expected ${expectedType}.`,
464+
);
510465
}
466+
467+
return {
468+
url: existingEntry.url || existingEntry.uri || targetUri,
469+
created: false,
470+
type: expectedType,
471+
};
511472
}
512-
currentUri = fullUri;
473+
474+
const createdUrl =
475+
expectedType === "file"
476+
? await fsOperation(baseUri).createFile(name)
477+
: await fsOperation(baseUri).createDirectory(name);
478+
479+
return {
480+
url: createdUrl || targetUri,
481+
created: true,
482+
type: expectedType,
483+
};
484+
};
485+
let firstCreatedPath = null;
486+
let firstCreatedType = null;
487+
let firstTargetUri = uri;
488+
489+
if (isExternalStorageUri) {
490+
baseFolder = decodeURIComponent(currentUri.split("%3A")[1].split("/")[0]);
513491
}
514-
let tileType;
515-
if (isFile && parts.length === 1) {
516-
tileType = "file";
517-
} else {
518-
const urlParts = currentUri.split("/");
519-
const pathParts = pathString.split("/");
520-
const pathStartIndex = urlParts.findIndex(
521-
(part) => part === pathParts[0],
492+
if (parts[0]) {
493+
firstTargetUri = getTargetUri(uri, parts[0], 0);
494+
}
495+
496+
for (let i = 0; i < parts.length; i++) {
497+
const isLastElement = i === parts.length - 1;
498+
const name = parts[i];
499+
const targetUri = getTargetUri(currentUri, name, i);
500+
const expectedType = getExpectedType(isLastElement);
501+
const entry = await ensureEntry(
502+
currentUri,
503+
targetUri,
504+
name,
505+
expectedType,
522506
);
523-
if (pathStartIndex !== -1) {
524-
const pathEndIndex = pathStartIndex + pathParts.length;
525-
urlParts.splice(pathStartIndex + 1, pathEndIndex - pathStartIndex - 1);
507+
508+
if (entry.created && firstCreatedPath === null) {
509+
firstCreatedPath = entry.url;
510+
firstCreatedType = expectedType;
526511
}
527-
currentUri = urlParts.join("/");
528-
tileType = "folder";
512+
513+
currentUri = entry.url;
529514
}
530-
return { uri: currentUri, type: tileType };
515+
516+
return {
517+
uri: firstCreatedPath || firstTargetUri,
518+
created: Boolean(firstCreatedPath),
519+
type:
520+
firstCreatedType || (isFile && parts.length === 1 ? "file" : "folder"),
521+
};
531522
},
532523
formatDownloadCount(downloadCount) {
533524
const units = ["", "K", "M", "B", "T"];

0 commit comments

Comments
 (0)