Skip to content

Commit d51ff87

Browse files
authored
Not found paths in prefix fs always treated as dir (#2002)
Gracefully handle prefix paths that don't exist, representing them as directories so they can be escaped from. Also removes the ".." file info from the backend, instead only creating it on the frontend
1 parent 9ef213f commit d51ff87

File tree

6 files changed

+79
-132
lines changed

6 files changed

+79
-132
lines changed

frontend/app/view/preview/directorypreview.tsx

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,6 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
757757
const [searchText, setSearchText] = useState("");
758758
const [focusIndex, setFocusIndex] = useState(0);
759759
const [unfilteredData, setUnfilteredData] = useState<FileInfo[]>([]);
760-
const [filteredData, setFilteredData] = useState<FileInfo[]>([]);
761760
const showHiddenFiles = useAtomValue(model.showHiddenFiles);
762761
const [selectedPath, setSelectedPath] = useState("");
763762
const [refreshVersion, setRefreshVersion] = useAtom(model.refreshVersion);
@@ -776,44 +775,53 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
776775
};
777776
}, [setRefreshVersion]);
778777

779-
useEffect(() => {
780-
const getContent = async () => {
781-
let entries: FileInfo[];
782-
try {
783-
const file = await RpcApi.FileReadCommand(
784-
TabRpcClient,
785-
{
786-
info: {
787-
path: await model.formatRemoteUri(dirPath, globalStore.get),
778+
useEffect(
779+
() =>
780+
fireAndForget(async () => {
781+
let entries: FileInfo[];
782+
try {
783+
const file = await RpcApi.FileReadCommand(
784+
TabRpcClient,
785+
{
786+
info: {
787+
path: await model.formatRemoteUri(dirPath, globalStore.get),
788+
},
788789
},
789-
},
790-
null
791-
);
792-
entries = file.entries ?? [];
793-
} catch (e) {
794-
setErrorMsg({
795-
status: "Cannot Read Directory",
796-
text: `${e}`,
797-
});
798-
}
799-
setUnfilteredData(entries);
800-
};
801-
getContent();
802-
}, [conn, dirPath, refreshVersion]);
790+
null
791+
);
792+
entries = file.entries ?? [];
793+
entries.unshift({
794+
name: "..",
795+
path: file?.info?.dir,
796+
isdir: true,
797+
modtime: new Date().getTime(),
798+
mimetype: "directory",
799+
});
800+
} catch (e) {
801+
setErrorMsg({
802+
status: "Cannot Read Directory",
803+
text: `${e}`,
804+
});
805+
}
806+
setUnfilteredData(entries);
807+
}),
808+
[conn, dirPath, refreshVersion]
809+
);
803810

804-
useEffect(() => {
805-
const filtered = unfilteredData?.filter((fileInfo) => {
806-
if (fileInfo.name == null) {
807-
console.log("fileInfo.name is null", fileInfo);
808-
return false;
809-
}
810-
if (!showHiddenFiles && fileInfo.name.startsWith(".") && fileInfo.name != "..") {
811-
return false;
812-
}
813-
return fileInfo.name.toLowerCase().includes(searchText);
814-
});
815-
setFilteredData(filtered ?? []);
816-
}, [unfilteredData, showHiddenFiles, searchText]);
811+
const filteredData = useMemo(
812+
() =>
813+
unfilteredData?.filter((fileInfo) => {
814+
if (fileInfo.name == null) {
815+
console.log("fileInfo.name is null", fileInfo);
816+
return false;
817+
}
818+
if (!showHiddenFiles && fileInfo.name.startsWith(".") && fileInfo.name != "..") {
819+
return false;
820+
}
821+
return fileInfo.name.toLowerCase().includes(searchText);
822+
}) ?? [],
823+
[unfilteredData, showHiddenFiles, searchText]
824+
);
817825

818826
useEffect(() => {
819827
model.directoryKeyDownHandler = (waveEvent: WaveKeyboardEvent): boolean => {

frontend/app/view/preview/preview.tsx

Lines changed: 4 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { BlockNodeModel } from "@/app/block/blocktypes";
55
import { Button } from "@/app/element/button";
66
import { CopyButton } from "@/app/element/copybutton";
77
import { CenteredDiv } from "@/app/element/quickelems";
8-
import { TypeAheadModal } from "@/app/modals/typeaheadmodal";
98
import { ContextMenuModel } from "@/app/store/contextmenu";
109
import { tryReinjectKey } from "@/app/store/keymodel";
1110
import { RpcApi } from "@/app/store/wshclientapi";
@@ -18,7 +17,7 @@ import * as services from "@/store/services";
1817
import * as WOS from "@/store/wos";
1918
import { getWebServerEndpoint } from "@/util/endpoints";
2019
import { goHistory, goHistoryBack, goHistoryForward } from "@/util/historyutil";
21-
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil";
20+
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
2221
import { addOpenMenuItems } from "@/util/previewutil";
2322
import { base64ToString, fireAndForget, isBlank, jotaiLoadableValue, makeConnRoute, stringToBase64 } from "@/util/util";
2423
import { formatRemoteUri } from "@/util/waveutil";
@@ -28,7 +27,7 @@ import { Atom, atom, Getter, PrimitiveAtom, useAtom, useAtomValue, useSetAtom, W
2827
import { loadable } from "jotai/utils";
2928
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
3029
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
31-
import { createRef, memo, useCallback, useEffect, useMemo, useState } from "react";
30+
import { createRef, memo, useCallback, useEffect, useMemo } from "react";
3231
import { TransformComponent, TransformWrapper, useControls } from "react-zoom-pan-pinch";
3332
import { CSVView } from "./csvview";
3433
import { DirectoryPreview } from "./directorypreview";
@@ -1073,17 +1072,17 @@ function PreviewView({
10731072
model: PreviewModel;
10741073
}) {
10751074
const connStatus = useAtomValue(model.connStatus);
1076-
const filePath = useAtomValue(model.metaFilePath);
10771075
const [errorMsg, setErrorMsg] = useAtom(model.errorMsgAtom);
10781076
const connection = useAtomValue(model.connectionImmediate);
10791077
const fileInfo = useAtomValue(model.statFile);
10801078

10811079
useEffect(() => {
1080+
console.log("fileInfo or connection changed", fileInfo, connection);
10821081
if (!fileInfo) {
10831082
return;
10841083
}
10851084
setErrorMsg(null);
1086-
}, [connection, filePath, fileInfo]);
1085+
}, [connection, fileInfo]);
10871086

10881087
if (connStatus?.status != "connected") {
10891088
return null;
@@ -1113,7 +1112,6 @@ function PreviewView({
11131112

11141113
return (
11151114
<>
1116-
{/* <OpenFileModal blockId={blockId} model={model} blockRef={blockRef} /> */}
11171115
<div key="fullpreview" className="full-preview scrollbar-hide-until-hover">
11181116
{errorMsg && <ErrorOverlay errorMsg={errorMsg} resetOverlay={() => setErrorMsg(null)} />}
11191117
<div ref={contentRef} className="full-preview-content">
@@ -1133,72 +1131,6 @@ function PreviewView({
11331131
);
11341132
}
11351133

1136-
const OpenFileModal = memo(
1137-
({
1138-
model,
1139-
blockRef,
1140-
blockId,
1141-
}: {
1142-
model: PreviewModel;
1143-
blockRef: React.RefObject<HTMLDivElement>;
1144-
blockId: string;
1145-
}) => {
1146-
const openFileModal = useAtomValue(model.openFileModal);
1147-
const curFileName = useAtomValue(model.metaFilePath);
1148-
const [filePath, setFilePath] = useState("");
1149-
const isNodeFocused = useAtomValue(model.nodeModel.isFocused);
1150-
const handleKeyDown = useCallback(
1151-
keydownWrapper((waveEvent: WaveKeyboardEvent): boolean => {
1152-
if (checkKeyPressed(waveEvent, "Escape")) {
1153-
model.updateOpenFileModalAndError(false);
1154-
return true;
1155-
}
1156-
1157-
const handleCommandOperations = async () => {
1158-
if (checkKeyPressed(waveEvent, "Enter")) {
1159-
await model.handleOpenFile(filePath);
1160-
return true;
1161-
}
1162-
return false;
1163-
};
1164-
1165-
handleCommandOperations().catch((error) => {
1166-
console.error("Error handling key down:", error);
1167-
model.updateOpenFileModalAndError(true, "An error occurred during operation.");
1168-
return false;
1169-
});
1170-
return false;
1171-
}),
1172-
[model, blockId, filePath, curFileName]
1173-
);
1174-
const handleFileSuggestionSelect = (value) => {
1175-
globalStore.set(model.openFileModal, false);
1176-
};
1177-
const handleFileSuggestionChange = (value) => {
1178-
setFilePath(value);
1179-
};
1180-
const handleBackDropClick = () => {
1181-
globalStore.set(model.openFileModal, false);
1182-
};
1183-
if (!openFileModal) {
1184-
return null;
1185-
}
1186-
return (
1187-
<TypeAheadModal
1188-
label="Open path"
1189-
blockRef={blockRef}
1190-
anchorRef={model.previewTextRef}
1191-
onKeyDown={handleKeyDown}
1192-
onSelect={handleFileSuggestionSelect}
1193-
onChange={handleFileSuggestionChange}
1194-
onClickBackdrop={handleBackDropClick}
1195-
autoFocus={isNodeFocused}
1196-
giveFocusRef={model.openFileModalGiveFocusRef}
1197-
/>
1198-
);
1199-
}
1200-
);
1201-
12021134
const ErrorOverlay = memo(({ errorMsg, resetOverlay }: { errorMsg: ErrorMsg; resetOverlay: () => void }) => {
12031135
const showDismiss = errorMsg.showDismiss ?? true;
12041136
const buttonClassName = "outlined grey font-size-11 vertical-padding-3 horizontal-padding-7";

pkg/remote/fileshare/fsutil/fsutil.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ func DetermineCopyDestPath(ctx context.Context, srcConn, destConn *connparse.Con
150150
srcInfo, err = srcClient.Stat(ctx, srcConn)
151151
if err != nil {
152152
return "", "", nil, fmt.Errorf("error getting source file info: %w", err)
153+
} else if srcInfo.NotFound {
154+
return "", "", nil, fmt.Errorf("source file not found: %w", err)
153155
}
154156
destInfo, err := destClient.Stat(ctx, destConn)
155157
destExists := err == nil && !destInfo.NotFound
@@ -158,7 +160,7 @@ func DetermineCopyDestPath(ctx context.Context, srcConn, destConn *connparse.Con
158160
}
159161
originalDestPath := destPath
160162
if !srcHasSlash {
161-
if destInfo.IsDir || (!destExists && !destHasSlash && srcInfo.IsDir) {
163+
if (destExists && destInfo.IsDir) || (!destExists && !destHasSlash && srcInfo.IsDir) {
162164
destPath = fspath.Join(destPath, fspath.Base(srcConn.Path))
163165
}
164166
}

pkg/remote/fileshare/s3fs/s3fs.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ func (c S3Client) ReadStream(ctx context.Context, conn *connparse.Connection, da
6262
return
6363
}
6464
rtn <- wshrpc.RespOrErrorUnion[wshrpc.FileData]{Response: wshrpc.FileData{Info: finfo}}
65+
if finfo.NotFound {
66+
rtn <- wshrpc.RespOrErrorUnion[wshrpc.FileData]{Response: wshrpc.FileData{Entries: []*wshrpc.FileInfo{
67+
{
68+
Path: finfo.Dir,
69+
Dir: fspath.Dir(finfo.Dir),
70+
Name: "..",
71+
IsDir: true,
72+
Size: 0,
73+
ModTime: time.Now().Unix(),
74+
MimeType: "directory",
75+
},
76+
}}}
77+
return
78+
}
6579
if finfo.IsDir {
6680
listEntriesCh := c.ListEntriesStream(ctx, conn, nil)
6781
defer func() {
@@ -455,20 +469,6 @@ func (c S3Client) ListEntriesStream(ctx context.Context, conn *connparse.Connect
455469
rtn <- wshutil.RespErr[wshrpc.CommandRemoteListEntriesRtnData](err)
456470
return
457471
}
458-
parentPath := fsutil.GetParentPath(conn)
459-
if parentPath != "" {
460-
rtn <- wshrpc.RespOrErrorUnion[wshrpc.CommandRemoteListEntriesRtnData]{Response: wshrpc.CommandRemoteListEntriesRtnData{FileInfo: []*wshrpc.FileInfo{
461-
{
462-
Path: parentPath,
463-
Dir: fsutil.GetParentPathString(parentPath),
464-
Name: "..",
465-
IsDir: true,
466-
Size: 0,
467-
ModTime: time.Now().Unix(),
468-
MimeType: "directory",
469-
},
470-
}}}
471-
}
472472
entries := make([]*wshrpc.FileInfo, 0, wshrpc.DirChunkSize)
473473
for _, entry := range entryMap {
474474
entries = append(entries, entry)
@@ -532,6 +532,7 @@ func (c S3Client) Stat(ctx context.Context, conn *connparse.Connection) (*wshrpc
532532
Path: bucketName,
533533
Dir: fspath.Separator,
534534
NotFound: true,
535+
IsDir: true,
535536
}, nil
536537
}
537538
}
@@ -556,7 +557,7 @@ func (c S3Client) Stat(ctx context.Context, conn *connparse.Connection) (*wshrpc
556557
MaxKeys: aws.Int32(1),
557558
})
558559
if err == nil {
559-
if entries.Contents != nil && len(entries.Contents) > 0 {
560+
if entries.Contents != nil {
560561
return &wshrpc.FileInfo{
561562
Name: objectKey,
562563
Path: conn.GetPathWithHost(),
@@ -575,6 +576,7 @@ func (c S3Client) Stat(ctx context.Context, conn *connparse.Connection) (*wshrpc
575576
Name: objectKey,
576577
Path: conn.GetPathWithHost(),
577578
Dir: fsutil.GetParentPath(conn),
579+
IsDir: true,
578580
NotFound: true,
579581
}, nil
580582
}

pkg/remote/fileshare/wavefs/wavefs.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ func (c WaveClient) Read(ctx context.Context, conn *connparse.Connection, data w
105105
if err != nil {
106106
return nil, fmt.Errorf("error listing blockfiles: %w", err)
107107
}
108+
if len(list) == 0 {
109+
return &wshrpc.FileData{
110+
Info: &wshrpc.FileInfo{
111+
Name: fspath.Base(fileName),
112+
Path: fileName,
113+
Dir: fspath.Dir(fileName),
114+
NotFound: true,
115+
IsDir: true,
116+
}}, nil
117+
}
108118
return &wshrpc.FileData{Info: data.Info, Entries: list}, nil
109119
}
110120

pkg/wshrpc/wshremote/wshremote.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,6 @@ func (impl *ServerImpl) remoteStreamFileDir(ctx context.Context, path string, by
108108
}
109109
}
110110
var fileInfoArr []*wshrpc.FileInfo
111-
parent := filepath.Dir(path)
112-
parentFileInfo, err := impl.fileInfoInternal(parent, false)
113-
if err == nil && parent != path {
114-
parentFileInfo.Name = ".."
115-
parentFileInfo.Size = -1
116-
fileInfoArr = append(fileInfoArr, parentFileInfo)
117-
}
118111
for _, innerFileEntry := range innerFilesEntries {
119112
if ctx.Err() != nil {
120113
return ctx.Err()

0 commit comments

Comments
 (0)