mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-02-28 03:42:50 +01:00
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:
parent
9ef213fc42
commit
d51ff87c26
@ -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 => {
|
||||
|
@ -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";
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user