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
This commit is contained in:
Evan Simkowitz 2025-02-21 16:32:14 -08:00 committed by GitHub
parent 9ef213fc42
commit d51ff87c26
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 79 additions and 132 deletions

View File

@ -757,7 +757,6 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
const [searchText, setSearchText] = useState("");
const [focusIndex, setFocusIndex] = useState(0);
const [unfilteredData, setUnfilteredData] = useState<FileInfo[]>([]);
const [filteredData, setFilteredData] = useState<FileInfo[]>([]);
const showHiddenFiles = useAtomValue(model.showHiddenFiles);
const [selectedPath, setSelectedPath] = useState("");
const [refreshVersion, setRefreshVersion] = useAtom(model.refreshVersion);
@ -776,8 +775,9 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
};
}, [setRefreshVersion]);
useEffect(() => {
const getContent = async () => {
useEffect(
() =>
fireAndForget(async () => {
let entries: FileInfo[];
try {
const file = await RpcApi.FileReadCommand(
@ -790,6 +790,13 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
null
);
entries = file.entries ?? [];
entries.unshift({
name: "..",
path: file?.info?.dir,
isdir: true,
modtime: new Date().getTime(),
mimetype: "directory",
});
} catch (e) {
setErrorMsg({
status: "Cannot Read Directory",
@ -797,12 +804,13 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
});
}
setUnfilteredData(entries);
};
getContent();
}, [conn, dirPath, refreshVersion]);
}),
[conn, dirPath, refreshVersion]
);
useEffect(() => {
const filtered = unfilteredData?.filter((fileInfo) => {
const filteredData = useMemo(
() =>
unfilteredData?.filter((fileInfo) => {
if (fileInfo.name == null) {
console.log("fileInfo.name is null", fileInfo);
return false;
@ -811,9 +819,9 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
return false;
}
return fileInfo.name.toLowerCase().includes(searchText);
});
setFilteredData(filtered ?? []);
}, [unfilteredData, showHiddenFiles, searchText]);
}) ?? [],
[unfilteredData, showHiddenFiles, searchText]
);
useEffect(() => {
model.directoryKeyDownHandler = (waveEvent: WaveKeyboardEvent): boolean => {

View File

@ -5,7 +5,6 @@ import { BlockNodeModel } from "@/app/block/blocktypes";
import { Button } from "@/app/element/button";
import { CopyButton } from "@/app/element/copybutton";
import { CenteredDiv } from "@/app/element/quickelems";
import { TypeAheadModal } from "@/app/modals/typeaheadmodal";
import { ContextMenuModel } from "@/app/store/contextmenu";
import { tryReinjectKey } from "@/app/store/keymodel";
import { RpcApi } from "@/app/store/wshclientapi";
@ -18,7 +17,7 @@ import * as services from "@/store/services";
import * as WOS from "@/store/wos";
import { getWebServerEndpoint } from "@/util/endpoints";
import { goHistory, goHistoryBack, goHistoryForward } from "@/util/historyutil";
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil";
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
import { addOpenMenuItems } from "@/util/previewutil";
import { base64ToString, fireAndForget, isBlank, jotaiLoadableValue, makeConnRoute, stringToBase64 } from "@/util/util";
import { formatRemoteUri } from "@/util/waveutil";
@ -28,7 +27,7 @@ import { Atom, atom, Getter, PrimitiveAtom, useAtom, useAtomValue, useSetAtom, W
import { loadable } from "jotai/utils";
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
import { createRef, memo, useCallback, useEffect, useMemo, useState } from "react";
import { createRef, memo, useCallback, useEffect, useMemo } from "react";
import { TransformComponent, TransformWrapper, useControls } from "react-zoom-pan-pinch";
import { CSVView } from "./csvview";
import { DirectoryPreview } from "./directorypreview";
@ -1073,17 +1072,17 @@ function PreviewView({
model: PreviewModel;
}) {
const connStatus = useAtomValue(model.connStatus);
const filePath = useAtomValue(model.metaFilePath);
const [errorMsg, setErrorMsg] = useAtom(model.errorMsgAtom);
const connection = useAtomValue(model.connectionImmediate);
const fileInfo = useAtomValue(model.statFile);
useEffect(() => {
console.log("fileInfo or connection changed", fileInfo, connection);
if (!fileInfo) {
return;
}
setErrorMsg(null);
}, [connection, filePath, fileInfo]);
}, [connection, fileInfo]);
if (connStatus?.status != "connected") {
return null;
@ -1113,7 +1112,6 @@ function PreviewView({
return (
<>
{/* <OpenFileModal blockId={blockId} model={model} blockRef={blockRef} /> */}
<div key="fullpreview" className="full-preview scrollbar-hide-until-hover">
{errorMsg && <ErrorOverlay errorMsg={errorMsg} resetOverlay={() => setErrorMsg(null)} />}
<div ref={contentRef} className="full-preview-content">
@ -1133,72 +1131,6 @@ function PreviewView({
);
}
const OpenFileModal = memo(
({
model,
blockRef,
blockId,
}: {
model: PreviewModel;
blockRef: React.RefObject<HTMLDivElement>;
blockId: string;
}) => {
const openFileModal = useAtomValue(model.openFileModal);
const curFileName = useAtomValue(model.metaFilePath);
const [filePath, setFilePath] = useState("");
const isNodeFocused = useAtomValue(model.nodeModel.isFocused);
const handleKeyDown = useCallback(
keydownWrapper((waveEvent: WaveKeyboardEvent): boolean => {
if (checkKeyPressed(waveEvent, "Escape")) {
model.updateOpenFileModalAndError(false);
return true;
}
const handleCommandOperations = async () => {
if (checkKeyPressed(waveEvent, "Enter")) {
await model.handleOpenFile(filePath);
return true;
}
return false;
};
handleCommandOperations().catch((error) => {
console.error("Error handling key down:", error);
model.updateOpenFileModalAndError(true, "An error occurred during operation.");
return false;
});
return false;
}),
[model, blockId, filePath, curFileName]
);
const handleFileSuggestionSelect = (value) => {
globalStore.set(model.openFileModal, false);
};
const handleFileSuggestionChange = (value) => {
setFilePath(value);
};
const handleBackDropClick = () => {
globalStore.set(model.openFileModal, false);
};
if (!openFileModal) {
return null;
}
return (
<TypeAheadModal
label="Open path"
blockRef={blockRef}
anchorRef={model.previewTextRef}
onKeyDown={handleKeyDown}
onSelect={handleFileSuggestionSelect}
onChange={handleFileSuggestionChange}
onClickBackdrop={handleBackDropClick}
autoFocus={isNodeFocused}
giveFocusRef={model.openFileModalGiveFocusRef}
/>
);
}
);
const ErrorOverlay = memo(({ errorMsg, resetOverlay }: { errorMsg: ErrorMsg; resetOverlay: () => void }) => {
const showDismiss = errorMsg.showDismiss ?? true;
const buttonClassName = "outlined grey font-size-11 vertical-padding-3 horizontal-padding-7";

View File

@ -150,6 +150,8 @@ func DetermineCopyDestPath(ctx context.Context, srcConn, destConn *connparse.Con
srcInfo, err = srcClient.Stat(ctx, srcConn)
if err != nil {
return "", "", nil, fmt.Errorf("error getting source file info: %w", err)
} else if srcInfo.NotFound {
return "", "", nil, fmt.Errorf("source file not found: %w", err)
}
destInfo, err := destClient.Stat(ctx, destConn)
destExists := err == nil && !destInfo.NotFound
@ -158,7 +160,7 @@ func DetermineCopyDestPath(ctx context.Context, srcConn, destConn *connparse.Con
}
originalDestPath := destPath
if !srcHasSlash {
if destInfo.IsDir || (!destExists && !destHasSlash && srcInfo.IsDir) {
if (destExists && destInfo.IsDir) || (!destExists && !destHasSlash && srcInfo.IsDir) {
destPath = fspath.Join(destPath, fspath.Base(srcConn.Path))
}
}

View File

@ -62,6 +62,20 @@ func (c S3Client) ReadStream(ctx context.Context, conn *connparse.Connection, da
return
}
rtn <- wshrpc.RespOrErrorUnion[wshrpc.FileData]{Response: wshrpc.FileData{Info: finfo}}
if finfo.NotFound {
rtn <- wshrpc.RespOrErrorUnion[wshrpc.FileData]{Response: wshrpc.FileData{Entries: []*wshrpc.FileInfo{
{
Path: finfo.Dir,
Dir: fspath.Dir(finfo.Dir),
Name: "..",
IsDir: true,
Size: 0,
ModTime: time.Now().Unix(),
MimeType: "directory",
},
}}}
return
}
if finfo.IsDir {
listEntriesCh := c.ListEntriesStream(ctx, conn, nil)
defer func() {
@ -455,20 +469,6 @@ func (c S3Client) ListEntriesStream(ctx context.Context, conn *connparse.Connect
rtn <- wshutil.RespErr[wshrpc.CommandRemoteListEntriesRtnData](err)
return
}
parentPath := fsutil.GetParentPath(conn)
if parentPath != "" {
rtn <- wshrpc.RespOrErrorUnion[wshrpc.CommandRemoteListEntriesRtnData]{Response: wshrpc.CommandRemoteListEntriesRtnData{FileInfo: []*wshrpc.FileInfo{
{
Path: parentPath,
Dir: fsutil.GetParentPathString(parentPath),
Name: "..",
IsDir: true,
Size: 0,
ModTime: time.Now().Unix(),
MimeType: "directory",
},
}}}
}
entries := make([]*wshrpc.FileInfo, 0, wshrpc.DirChunkSize)
for _, entry := range entryMap {
entries = append(entries, entry)
@ -532,6 +532,7 @@ func (c S3Client) Stat(ctx context.Context, conn *connparse.Connection) (*wshrpc
Path: bucketName,
Dir: fspath.Separator,
NotFound: true,
IsDir: true,
}, nil
}
}
@ -556,7 +557,7 @@ func (c S3Client) Stat(ctx context.Context, conn *connparse.Connection) (*wshrpc
MaxKeys: aws.Int32(1),
})
if err == nil {
if entries.Contents != nil && len(entries.Contents) > 0 {
if entries.Contents != nil {
return &wshrpc.FileInfo{
Name: objectKey,
Path: conn.GetPathWithHost(),
@ -575,6 +576,7 @@ func (c S3Client) Stat(ctx context.Context, conn *connparse.Connection) (*wshrpc
Name: objectKey,
Path: conn.GetPathWithHost(),
Dir: fsutil.GetParentPath(conn),
IsDir: true,
NotFound: true,
}, nil
}

View File

@ -105,6 +105,16 @@ func (c WaveClient) Read(ctx context.Context, conn *connparse.Connection, data w
if err != nil {
return nil, fmt.Errorf("error listing blockfiles: %w", err)
}
if len(list) == 0 {
return &wshrpc.FileData{
Info: &wshrpc.FileInfo{
Name: fspath.Base(fileName),
Path: fileName,
Dir: fspath.Dir(fileName),
NotFound: true,
IsDir: true,
}}, nil
}
return &wshrpc.FileData{Info: data.Info, Entries: list}, nil
}

View File

@ -108,13 +108,6 @@ func (impl *ServerImpl) remoteStreamFileDir(ctx context.Context, path string, by
}
}
var fileInfoArr []*wshrpc.FileInfo
parent := filepath.Dir(path)
parentFileInfo, err := impl.fileInfoInternal(parent, false)
if err == nil && parent != path {
parentFileInfo.Name = ".."
parentFileInfo.Size = -1
fileInfoArr = append(fileInfoArr, parentFileInfo)
}
for _, innerFileEntry := range innerFilesEntries {
if ctx.Err() != nil {
return ctx.Err()